effective-java-2nd
Table of Contents
- Chapter 2: Creating and Destroying Objects
- Item 1: Consider static factory methods instead of constructors
- Item 2: Consider a builder when faced with many constructor parameters
- Item 3: Enforce the singleton property with a private constructor or an enum type
- Item 4: Enforce noninstantiability with a private constructor
- Item 5: Avoid creating unnecessary objects
- Item 6: Eliminate obsolete object references
- Item 7: Avoid finalizers
- Chapter 3: Methods Common to All Objects
- Chapter 4: Classes and Interfaces
- Item 13: Minimize the accessibility of classes and members
- Item 14: In public classes, use accessor methods, not public fields
- Item 15: Minimize mutability
- Item 16: Favor composition over inheritance
- Item 17: Design and document for inheritane or else prohibit it
- Item 18: Perfer interfaces to abstract classes
- Item 19: Use interfaces only to define types
- Item 20: Prefer class hierarchies to tagged classes
- Item 21: Use function objects to represent strategies
- Item 22: Favor static member classes over nonstatic
- Chapter 5: Generics
Chapter 2: Creating and Destroying Objects
Item 1: Consider static factory methods instead of constructors
- class实例化的常规做法是使用public ctor, 但是class提供一个public static factory method给client使用是更好的办法
- 比如为class Boolean是使用下面的办法把一个boolean primitive值转化成Boolean
object reference的
public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
- 需要注意的是static factory method和设计模式里面的"Factory Method"没有直接联系
- 使用static factory method是优缺点并存的:
- 优点1是: 和ctor不同的是static factory method是有名字的. 比起ctor来就更容 易使用
- 优点2是: 和ctor不同, static factory method不需要每次都创建新的object: 这 个特点可以被immutable class使用来"预先创建instance"(preconstructed instances), 或者是cache他们已经创建的对象.比如Boolean.valueOf(boolean)就是这样(因为这 个函数是返回已经创建的instance,从来不会新创建instance
- 优点3是: static factory method可以:
- 返回"函数返回类型"的"子类的object".
- 返回的object,其class是private的
- 我们以Collection为例子来看看
- 首先, Collection是一个interface
public interface Collection<E> extends Iterable<E>
- 其次, Collections是一个class,而且private ctor
public class Collections { // Suppresses default constructor, ensuring non-instantiability. private Collections() { } // .... }
- Collections里面都是返回一些继承Collection interface的interface, 比如List
就是在函数定义返回值的时候,定义了一些'父类'(List).实际返回的时候,可以
根据情况返回'子类'(UnmodifiableRandomAccessList或者UnmodifiableList)
public static <T> List<T> unmodifiableList(List<? extends T> list) { return (list instanceof RandomAccess ? new UnmodifiableRandomAccessList<>(list) : new UnmodifiableList<>(list)); }
- Collections里面,会返回一些private的class的object. 就是通过我们的static
factory method.
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) { return new SynchronizedMap<>(m); } /** * @serial include */ private static class SynchronizedMap<K,V> implements Map<K,V>, Serializable { private static final long serialVersionUID = 1978198479659022715L; // ... }
- 首先, Collection是一个interface
- 从上面的例子可以看出, static factory method设计出来的代码,用户能够知道的就 是返回值是一个接口(父类), 而不用去关心其具体到底底层是什么实现,这通常来说 都是良好的设计实践: 因为下一个版本,我甚至是可以更改底层的实现,但是对用户来 说没有影响,因为原来只承诺了接口
- #TODO#
- 优点4是:创建instance的时候,可以减少冗长的参数:
- 最开始是这样创建一个map
Map<String, List<String>> m = new HashMap<String, List<String>>();
- 使用了static factory method后,可以简化成这样
Map<String, List<String>> m = HashMap.newInstance();
- static factory method当然得如外提供啦
public static <K, V> HashMap<K, V> newInstance() { return new HashMap<K, V>(); }
- 最开始是这样创建一个map
- 当然了,缺点也是存在的:
- 缺点1: 如果只依靠static factory method的话,那么ctor就是private的,所以他们 就无法被实例化
- 缺点2: static factory method和其他的static函数没有明显区别,所以用户想用来
创建instance的时候,往往很难寻找.下面是我们建议的几个名字:
- valueOf:返回一个和它参数一样的instance,通常用来类型转换
- of: 是valueOf的另外一种称呼
- getInstance: 如果有参数,那么就返回跟参数描述一致的instance. 如果是单例 模式(Singleton中), 那么就是没有参数,返回唯一存在的instance
- newInsstance: 和getInstance一样,不过保证返回的instance和其他的instance 完全不同
- getType: 返回值为Type类型的getInstance
- newType: 返回值为Type类型的newInstance
Item 2: Consider a builder when faced with many constructor parameters
- 固定参数的ctor和item1介绍的factory来初始化object的时候,有一个致命的缺点:在
参数很多的情况下,扩展行不好,很容易用错.
// Does not scale well! public class NutritionFacts { private final int servingSize; // required private final int servings; // required private final int calories; // optional private final int fat; // optional private final int sodium; // optional private final int carbohydrate; // optional public NutritionFacts(int servingSize, int servings) { this(servingSize, servings, 0); } public NutritionFacts(int servingSize, int servings, int calories) { this(servingSize, servings, calories, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat) { this(servingSize, servings, calories, fat, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) { this(servingSize, servings, calories, fat, sodium, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) { this.servingSize = servingSize; this.servings = servings; this.calories = calories; this.fat = fat; this.sodium = sodium; this.carbohydrate = carbohydrate; } }
- 上面的例子中有两个必要的域,四个可选的域, 然后就出现了下面的实现方式: 多个ct
or,然后每个ctor里面去调用"下一个ctor", 这种方法叫做
telescoping constructor pattern (重叠构造器模式)
- telescoping ctor的缺点有很多:
- ctor非常难以书写,像绕口令一样
- 非常难以阅读
- 容易用错(特别是参数太多,不小心交换位置的情况,编译器不会报错)
- 除了telescoping 模式以外,在多参数的class里面,我们还可以通过JavaBean模式来设
置:设置一个没参赛的ctor,然后分别使用函数设置成员变量
public class NutritionFacts { // Parameters initialized to default values (if any) private int servingSize = -1; // Required; no default value private int servings = -1; private int calories = 0; private int fat = 0; private int sodium = 0; private int carbohydrate = 0; public NutritionFacts() { } // Setters public void setServingSize(int val) { servingSize = val; } public void setServings(int val) { servingSize = val; } public void setCalories(int val) { calories = val; } public void setFat(int val) { fat = val; } public void setSodium(int val) { sodium = val; } public void setCarbohydrate(int val) { carbohydrate = val; } }
- 不幸的是JavaBean模式有着非常严重的缺点:
- JavaBean模式可能在"构建"的过程中,经历inconsistent state的状态
- 由于inconsistent state的存在, immutable class就变得不再可能
- 需要额外的工作来保证程序的线程安全
- 在现有的Java语言特点下,多参数(并且很多是optional参数的)ctor的最佳实践是: Builder pattern
- 下面是builder pattern的一个例子
public class NutritionFacts { private final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate; public static class Builder { // Required parameters private final int servingSize; private final int servings; // Optional parameters - initialized to default values private int calories = 0; private int fat = 0; private int carbohydrate = 0; private int sodium = 0; public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; } public Builder calories(int val) { calories = val; return this; } public Builder fat(int val) { fat = val; return this; } public Builder carbohydrate(int val) { carbohydrate = val; return this; } public Builder sodium(int val) { sodium = val; return this; } public NutritionFacts build() { return new NutritionFacts(this); } } private NutritionFacts(Builder builder) { servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; fat = builder.fat; sodium = builder.sodium; carbohydrate = builder.carbohydrate; } }
- 使用方法如下
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8). calories(100).sodium(35).carbohydrate(27).build()
- Java的代码的确就是长啊,说白了,这就是模拟python的"Optional parameter & Named paramter"
def info(object, spacing=10, collapse=1): pass # With only one argument, spacing gets its default value of 10 and collapse gets # its default value of 1 info(odbchelper) # With two arguments, collapse gets it default value of 1 info(odbchelper, 12) # Here you are naming the collapse argument explicitly and specifying its value, # spacing still gets its default value of 10 info(odbchelper, collapse=0) # Even required arguments(like object, which has no defult value) can be named, # and named arguments can appear in any order info(spacing=15, object=odbchelper)
- #TODO#
Item 3: Enforce the singleton property with a private constructor or an enum type
- 所谓singleton就是某种只能实例化一次的class. 某些系统组件(比如Windows manager) 是唯一的,重复实例化它们没有意义
- 在java1.5以前,我们有两种方法来实现singleton:
- 方法一:把ctor设置为private, 但是把自己的public static member露出来. (private
的ctor可能会被调用的方法只有一个就是AccessibleObject.setAccessible的帮助.
如果很小心的用户,可以通过设置ctor被两次调用就抛异常来解决这个问题)
public class Elvis { public static final Elvis INSTANCE = new Elvis(); private Elvis() { //... } public void leaveTheBuilding() { //.. } }
- 方法二:和方法一类似只不过露出来的是static factory method(item1介绍)
// Singleton with static factory public class Elvis { private static final Elvis INSTANCE = new Elvis(); private Elvis() { //... } public static Elvis getInstance() { return INSTANCE; } public void leaveTheBuilding() { //... } }
- 方法一:把ctor设置为private, 但是把自己的public static member露出来. (private
的ctor可能会被调用的方法只有一个就是AccessibleObject.setAccessible的帮助.
如果很小心的用户,可以通过设置ctor被两次调用就抛异常来解决这个问题)
- public member的方法仅仅是让用户更容易理解自己"singleton"的形象而已,在效率上
面没有提高,因为
Modern JVM implementations are almost certain to inline the call to the static factory method
- 而static factory method暴露了接口,没有暴露内部实现,更利于以后修改.比如,以后 可以为每一个thread都返回一个unique的instance.
- static factory method的方法还有利于generic type
- 但是,static factory method的两种优点都不是很重要,所以,还是使用public 成员的 方法应用更为广泛.
- 上面两种方法无论使用哪一种,如果实现了Serializable接口,都要做下面的操作来防止
singleton状态"在反序列化的时候"被破坏:
- 把所有变量声明为transient
- 提供一个readResolve函数
private Object readResolve() { // Return the one true Elvis and let the argabe collector // take care of the Elvis imersonator. return INSTANCE; }
- 到了java1.5以后,使用"单元素枚举类型"来实现singleton是best practice. 他不仅
形式简单,还可以规避"反序列"时候可能造成的问题
// Enum singleton - the preferred approach public enum Elvis { INSTANCE; public void leaveTheBuilding() { //... } }
- 网上一个比较全的实现如下
enum Singleton { INSTANCE; // instance vars, constructor private final Connection connection; Singleton() { // Initialize the connection connection = DB.getConnection(); } // Static getter public static Singleton getInstance() { return INSTANCE; } public Connection getConnection() { return connection; } }
Item 4: Enforce noninstantiability with a private constructor
- 有时候,你会想创建一个class,然后这个class里面只有static method和field. 这种
class,确有其价值.比如
- java.lang.Math
- java.util.Arrays
- 这种class另外的用法就是:class作用只是聚集static method(包括static factory method), 比如java.util.Collections
- 无论是上面哪种情况, class都是utility类,都没有实例化的必要.但是
妄图通过设置class为abstract来组织class被实例化的方法是不正确的
- 因为设置了abstract,subclass继承了它以后,还是可以实例化. 更重要的是,设置了 abstract的话,用户会以为这个类设计了就是为了继承的.
- 正确的做法是:把类的ctor设计成private的(其实effective cpp里面也有相似章节,只 不过java源代码里面有诸多良好的例子)
- 下面就是一个防止实例化的代码例子(其实代码中的Math, Arrays, Collections都是
private ctor)
// Noinstantiable utility class public class UtilityClass { // SUpress default ctor for noninstantiablity private UtilityClass() { throw new AssertionError(); } // Remainder omitted }
- 上面的throw new AssertionError()其实并不是必须.其作用是防止自身在后面的代码 中不小心调用ctor
- 设计了private ctor的另外一个副作用是:继承是肯定没法实现了.因为继承都要"依次" 调用父类的ctor,父类都private了.自然无法继承
Item 5: Avoid creating unnecessary objects
- 如果可以的话,重用一个object总是非常高效而且更直观. 如果一个object是immutable 的话, 那么它总是可以重用的
- 下面是一个非常反面的例子.因为每次执行这个语句的时候都会创建一个String instance,
而且参数"stringette"本身就是一个String instance. 如果在一个循环里面的话,会产生
非常多的instance
String s = new String("stringette"); // DON"t DO THIS
- 正确的做法是如下: 因为只有一个instance(参数"stringette"), 所以在循环里面也不会
多创建instance.同时, 如果同一个虚拟机如果还有其他字符串叫"stringette"的话,还可
以重用这个String instance
String s = "stringette";
- 避免创建不必要的object的话, 使用immutable class的ctor来创建一个对象是可以的,
但是最好的方法是:使用static factory method!
Boolean b1 = new Boolean("true"); // Good Boolean b2 = Boolean.valueOf("true"); // Better!
- 如果你的object虽然是mutable的,但是你知道他不会改变,那么你也可以重用!(当然要 非常小心).
- 下面是一个不太合理的例子: 因为每次调用都会创建一个Calendar, TimeZone 和两个
Date instance
public class Person { private final Date birthDate; // Other fields, methods, and constructor omitted // DON'T DO THIS! public boolean isBabyBoomer() { // Unnecessary allocation of expensive object Calendar gmtCal= Calendar.getInstance(TimeZone.getTimeZone("GMT")); gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0); Date boomStart = gmtCal.getTime(); gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0); Date boomEnd = gmtCal.getTime(); return birthDate.compareTo(boomStart) >= 0 && birthDate.compareTo(boomEnd) < 0; } }
- 我们来更改一下, 减少一下不必要object的创建. 下面例子中, Calendar, TimeZone和
Date都只创建了一次(在类实例化的时候), 而不是每次 isBabyBoomer调用的时候都创建
一套.
class Person { private final Date birthDate; // Other fields, methods, and constructor omitted ///////////////////////////////////////////////////// // The starting and ending dates of the baby boom. // ///////////////////////////////////////////////////// private static final Date BOOM_START; private static final Date BOOM_END; static { Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0); BOOM_START = gmtCal.getTime(); gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0); BOOM_END = gmtCal.getTime(); } public boolean isBabyBoomer() { return birthDate.compareTo(BOOM_START) >= 0 && birthDate.compareTo(BOOM_END) < 0; } }
- 上面的改动不仅仅体现在performance上面, 而且把身为local variable 的boomStart, boomEnd改成static final 的BOOM_START, BOOM_END, 也让他们的const本性一览无余
- 当然,如果isBabyBoomer函数从来没有被调用的话,那么BOOM_START, BOOM_END的创建
就没有必要了. 如果想进一步优化,可以使用lazy initialization BOOM_START, BOOM_END
但是!这个方法不推荐,因为:
- lazy initialization总是会让视线变得复杂
- lazy initialization也不会有太明显的性能提高
- 上面的例子中,重用的部分的"不变性"是很明显的,但是有些情况下,"不变性"不容易发现:
- 适配器模式(adapter): 所谓适配器是这样一个对象: 它把功能委托给一个后备对象(backing object), 从而让后备对象有了一个"新的接口". 但是适配器除了和后备对象有联系外, 没有任何其他的关联.所以,为某一个对象(given object)没必要提供超过一个的adapter object
- Map接口的keySet方法返回该Map对象的Set试图.看起来好像每次都要创建一个Set实例. 而实际的情况确实, keySet是和Map紧密联系的, 一旦Map变了,所有keySet的返回值 都变了.所以其实keySet返回的都是同一个Set instance
- Java 1.5里面有一个新特性: 自动装箱(autoboxing): 它会自动帮助程序员把"基本类 型"和"装箱类型(boxed primitive type)"混用.但是如果你不小心的话,会暗地里创建 不必要的对象!
- 比如下面的例子,不小心把long写成了Long, 会造成程序多创建了2^31个多余的Long实
例. 这也告诉我们,要优先使用基本类型,而不是装箱基本类型, 当心无意识的自动装箱
// Hideously slow program! Can you spot the object creation? public static void main(String[] args) { Long sum = 0L; for (long i = 0; i < Integer.MAX_VALUE; i++) { sum += i; } System.out.printf("sum"); }
Item 6: Eliminate obsolete object references
- 虽然Java帮助你进行内存管理,但这并不意味着Java就没有内存泄露
// Can you spot the "memory leak"? public class Stack { private Object[] elements; private int size = 0; private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack() { elements = new Object[DEFAULT_INITIAL_CAPACITY]; } public void push(Object e) { ensureCapacity(); elements[size++] = e; } public Object pop() { if (size == 0) { throw new EmptyStackException(); } return elements[--size]; } // Ensure space for at least one more element // roughly doubling the capacity each time the array // needs to grow private void ensureCapacity() { if (element.length == size) { elements = Arrays.copyOf(elements, 2 * size + 1); } } }
- 上面的程序是一个Java写的Stack, 它内存泄露的原因是:一开始stack会增长. 然后pop 弹出后会减小.循环往复.但是虽然element被弹出了. 但是! 这些element不会被GC!
- 这些元素没有被GC的原因是stack再pop的时候,element[size]这个reference还一直
指向那个object(这种reference不会被在反引用,所以叫做obsolete reference). 知道
了原因,那么解决起来就很简单了
public Object pop() { if (size == 0) { throw new EmptyStackException(); } Object result = elements[--size]; elements[size] = null; // Eliminate obsolete reference return result; }
- 这种把object reference值零的做法应该是一种例外,而不是常态.出现的原因在于
Stack这个程序管理了自己的内存. 也就是elements数值. 数组size以下的部分是分配好的, 其他部分是free的.但是GC不知道!
- 另外一个重要的memory leak的地方是cache: 一旦你把object引用放到cache里面以后, 很容易忘记它,从而导致它在cache里面很久,而相应内存无法释放.
- 解决这种问题的"天然"选择就是WeakHashMap. 这个数据结构简直就是天生为这个情况
设计的:
WeakHashMap,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此 map会自动丢弃此值(注意,是对key的引用,不是value)
- 内存泄露常见的第三种情况就是Listener和回调函数. 如果你实现了一个API, 客户端 在这个API中注册回调,却没有显式的取消注册.那么就会产生内存泄露. 解决的方法是 存储"这些callback函数的object reference"到WeakHashMap里面作为key.
Item 7: Avoid finalizers
- 上来先对finalizer来个"盖棺定论"
Finalizers are unpredictable, often dangerous, and generally unnecessary.
- C++用户首先要注意的是不要把Java的finalizer想成是C++里面的dtor:
- dtor有和try-finally block一致的部分:处理nonmemory resource
- 相对应的,dtor也有和try-finally block不一致的部分:处理memory resource
- finalizer最大的问题在于JVM设计的时候没有对它们的运行做出保证:这也就是说,一个 object变的不可用,到它被推入finalizer进行处理,中间间隔的时间会是"任意的长"
- 正是由于这种时间的不确定性,在finalizer里面是绝不能处理time-critical是绝不能
容忍的.举个例子:
- 我们知道一个进程能够拥有的file descriptor的数目是一定的.
- 如果一个进程在finalizer里面close file descriptor,那么就很可能发生过很久才 去执行finalizer里面的close代码,而进程里面打开了超过自己能力的file descriptor 最终导致进程崩溃
- 及时的执行finalizer是JVM的责任,而不同的JVM对待finalizer的态度不同,实现不同. 会导致最后在一个JVM里面运行良好的(含有finalizer)的代码在另外一个JVM里面失败
- Java语言规范不是"不保证"finalizer的及时运行,而是"根本不能保证实际运行与否!", 所以,如果出现了program在没有运行finalizer的情况下就terminate,你也不要惊奇.
- 这也就意味着
不能依靠finalizer去释放重要的资源比如数据库的锁(lock)
- 不要被System.gc和System.runFinalization所诱惑, 它们只是增加了finalizer执行 的机会.并不是真正能够finalize
- 曾经有能强制finalize的函数System.runFinalizersOnExit,但是都因为它们致命的缺 陷而被废弃了
- Finalizer另外一个大问题在于:
Finalizer因为在try-catch之后,已经脱离了exception的运行机制控制. 这也就是说如果在 finalizer里面出现了异常,那么这个异常是会被ignored,从而导致很多无法预测的问题!
- Finalizer还会导致"严重的性能下降"
- Finalizer已经是众矢之的了,在绝大部分情况下,我们要避免使用他们.那么我们如何释
放对象的某些资源(比如文件)呢? 方法是:
- "提供一个明确的termination method"
- 在资源不需要的时候自己调用termination method,并去private filed里面设置自己 的状态
- 其他函数运行之前要先去查看private filed里面确认这个资源还在.否则抛出异常: IllegalStateException
- 这种方法广泛的在java里面运用,比如InputStream,OutputStream和java.util.Connection 里面都有一个close()的函数.
- termination method通常还会和try-finally block合用,保证如果object被使用的时候
出了异常的话termination也能进行
// try-finally block guarantees execution of termination method Foo foo = new Foo(...); try { // Do what must be done with foo //.. } finally { foo.terminate(); // Explicit termination method }
- 最后的最后,来看看finalizer勉强可以算作还有用的两个场景:
- 作为安全网:
- 一个资源释放的晚总比不释放好,finalizer作为termination method以外的最后 一道屏障.事实上FileInputStream, FileOutputStream, Connection等都是有 try-finally block在里面再次调用termination method.
- finalizer除了要起到"再次释放"资源的目的以外,还应该加一个log. 因为在finalizer 里面调用"释放资源"是不对的.所以应该记录异常事件,把这种情况作为一个bug. 很遗憾FileInputSream等的try-finally block代码里面没有这么做
- 释放没有重要资源的native peer (TODO)
- 作为安全网:
- 实在要使用finalizer的话,也要知道finalizer如果含有继承的话,继承关系是不会自动
调用的.要自己手动写super.finalize()
//Manual finalizer chaining @Override protected void finalize() throws Throwable { try { ... // Finalize subclass state } finally { super.finalize(); } }
Chapter 3: Methods Common to All Objects
Item 8: Obey the general contract when overriding equals
- 实现equals是一件非常困难的事情,实现的不好就会造成灾难,所以最好是"能不实现,就
不实现", 满足下面任意一个条件,就可以不用"自己实现equals":
- Each instance of the class is inherently unique: 比如Thread这种,其对象肯 定是实例,而不是value,那么唯一性是肯定的.Object里面的equals实现就够用了
- You don't care whether the class provides a "logical euqality" test: 比 如你可以为java.util.Random实现一个equals来对比两次实现的随机数是否一样. 但显然这个设计是多余的,没有人会用这个功能
- A superclass has already overridden equals, and the superclass behavior is appropriate for this class: 比如大多数Set的具体实现都继承于AbstractSet. List的实现继承于AbstractList, Map的实现继承于AbstractMap
- class是private或者是package-private的,然后你确信这个equals method不会有人
调用,保险起见, 你还要加上如下代码
@Override public boolean equals(Object o) { throw new AssertionError(); // Method is never called }
- 那什么情况下要实现equals呢? 具体来说就是superclass没有实现equals 的value class: 所谓value class 就是class有'值'(比如Integer, Date). 用户比较这种class的instance 的时候,不想比较他们'是否refer to the same object', 而是想比较他们'是否逻辑上相等'
- 需要说明的是使用"instance control(就是保证class的一种value只有一个instance 的情况)"的class是不需要equals的, 比如Enum type, 对他们来说逻辑上相对,就意味 着是同一个intance
- #TODO#
Item 9: Always override hashCode when you override equals
- 如果要实现equals, 就必须实现hashCode, 这个是两者的合同契约关系: 如果两个object 通过equals method证明为相同的话,那么他们的hashCode method返回的integer也必须 相同
- 对于value class来说,如果实现了两者的equals method, 那么也要改写hashCode, 因
为对于hashCode来说.两者只是不同instance而已. 如果忘记实现hashCode, 那么你的
class的instance就无法作为Hash类(比如HashMap, HashSet, HashTable)的key啦
public final clas PhoneNumber { private final short areaCode; private final short prefix; private final short lineNumber; public PhoneNumber(int areaCode, int prefix, int lineNumber) { rangeCheck(areaCode, 999, "area code"); rangeCheck(prefix, 999, "prefix"); rangeCheck(lineNumber, 999, "line number"); this.areaCode = (short) areaCode; this.prefix = (short) prefix; this.lineNumber = (short) lineNumber; private static void rangeCheck(int arg, int max, String name) { if (arg < 0 || arg > max) { throw new IllegalArgumentException(name + ": " + arg); } } @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof PhoneNumber)) { return false; } PhoneNumber pn = (PhoneNumber)o; return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areCode; } // Broken - no hashCode method! } }
- 不要为了提高性能而把object里面的关键部分去掉再做hashCode.
- 1.2版本以前的String hashCode不会检查所有的成员char,而且均匀的选择16个字符 进行哈希,得到hashCode. 这样一来虽然相同的字符串一定会产生相同的hashCode, 但是某些不同的字符串(虽然概率较低)也会产生相同的hashCode.
- 这样导致散列函数把String作为key的时候, 如果key的数量足够大,那么不同字符串 就会产生很多相同的hashCode,最终导致Hash系列的查找非常的慢(因为有很多的冲突!)
- 1.2版本以后的String已经开始使用所有的char啦
public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
Item 10: Always override toString
- 虽然不如equals和hashCode的约定那么重要,但是为每一个子类实现toString函数是非 常好的一个建议
- 是否要把toString的格式在文档中体现出来是很重要的一个决定:
- 优点: 易于阅读
- 缺点: 如果这个类被广泛使用,那么一旦指定格式,就必须坚持,因为程序员会根据这 个格式编写了代码
Item 11: Override clone judiciously
- Cloneable接口是一个设计上的失败, 因为作为借口,它里面什么内容都没有
package java.lang; /** * A class implements the <code>Cloneable</code> interface to * indicate to the {@link java.lang.Object#clone()} method that it * is legal for that method to make a * field-for-field copy of instances of that class. * <p> * Invoking Object's clone method on an instance that does not implement the * <code>Cloneable</code> interface results in the exception * <code>CloneNotSupportedException</code> being thrown. * <p> * By convention, classes that implement this interface should override * <tt>Object.clone</tt> (which is protected) with a public method. * See {@link java.lang.Object#clone()} for details on overriding this * method. * <p> * Note that this interface does <i>not</i> contain the <tt>clone</tt> method. * Therefore, it is not possible to clone an object merely by virtue of the * fact that it implements this interface. Even if the clone method is invoked * reflectively, there is no guarantee that it will succeed. * * @author unascribed * @see java.lang.CloneNotSupportedException * @see java.lang.Object#clone() * @since JDK1.0 */ public interface Cloneable { }
- 它只是"声明"这个对象可以被克隆,但具体怎么克隆是类设计者的事情,类设计者可以 根本就不去实现一个clone函数, 而这个错误直到runtime才会被发现.如果把clone函 数放在Cloneable接口里面的话,无疑会更加的合理,因为"implement一个接口,但是没有 重载它的函数"本身就是一个错误.
- 那Cloneable为什么这么设计呢? 这是因为clone这个函数被定义在了Object里面(具体
实现是使用的c语言)
// cpp implementation can be found here // http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/9b0ca45cd756/src/share/vm/prims/jvm.cpp protected native Object clone() throws CloneNotSupportedException;
- Cloneable的设计还充满了以下各种问题:
- 与final域的正常使用冲突(final域只能初始化,无法赋值)
- 会抛出不必要的checked exception(CloneNotSupportedException).
- 实现深拷贝或者浅拷贝有赖于文档
- 总结起来就是:不要使用Cloneable接口,而应该使用拷贝构造器(copy ctor)或者拷贝
工厂(copy factory)
// copy ctor public Yum(Yum yum); // copy factory public static Yum newInstance(Yum yum);
Item 12: Consider implementing Comparable
- Comparable的设计颇为合适,它有自己的interface,然后interface里面还有函数
compareTo(而不是像Cloneable一样,有一个空的interface)
public interface Comparable<T> { int compareTo(T t); }
- 如果一个class实现了Comparable,意味着它遵守如下的承诺:
这个class的instance拥有了natural ordering的能力
- 所谓natural ordering能力,就是作为a, Arrays.sort(int[] a)就可以成功排序,不需
要一个comparator
public static <T> void sort(T[] a, Comparator<? super T> c)
- 我们常见的implements了Comparable,从而拥有natural ordering能力的就是digit和
String
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { //.... }
- 如果你想改变"natural ordering",那么就要implement Comparator, 实现其compare函
数. 注意,在使用的时候,如果没有加Comparator的参数的话,默认是按照Comparable来
实现的(因为它是"natural"的);
package org.hfeng.misc.ej2.item12; import java.util.Arrays; import java.util.Comparator; class Player implements Comparable<Player>, Comparator<Player>{ private int _number; private String _name; Player(int number, String name) { _number = number; _name = name; } Player() { } public String toString() { return String.format("No:%d-Name: %s", _number, _name); } public int compare(Player p, Player p2) { return p._name.compareTo(p2._name); } public int compareTo(Player p) { return this._number - p._number; } } public class TestOrder { public static void main(String[] args) { Player[] players = new Player[]{new Player(1, "Allen"), new Player(2, "Wade"), new Player(3, "Benson")}; Arrays.sort(players); System.out.println(Arrays.toString(players)); Arrays.sort(players, new Player()); System.out.println(Arrays.toString(players)); } } //////////////////////////////////////////////////////////// // <===================OUTPUT===================> // // [No:1-Name: Allen, No:2-Name: Wade, No:3-Name: Benson] // // [No:1-Name: Allen, No:3-Name: Benson, No:2-Name: Wade] // ////////////////////////////////////////////////////////////
Chapter 4: Classes and Interfaces
Item 13: Minimize the accessibility of classes and members
- 良好的设计首先就是要将内部实现和外部接口(API)完全分开.这个也是软件设计准则 里面"封装(encapsulation)"的要求
- 封装可以使得软件各个部分能够分开进行测试,开发,优化.但是"不能"提高软件的性能, 提高的只是开发效率,以及分析系统性能瓶颈时候的效率:一旦开发完毕测试出系统瓶颈 在哪里,就可以专注优化瓶颈部分
- 只对于java来说,如下的几点可以帮助我们更好的实现"封装"成员
- 尽可能的使每个类或者成员不被外界访问:
- 对于top level的class或者interface来说, 只有两个选择package-private或者 是public. 如果能够选择package-private,那么毫不犹豫的选择package-private. 因为package-private使得你的代码是"实现"的一部分,而不是"API"的一部分,成 为API总是要付出责任的.
- 如果某个类只是被"一个类"在内部使用,那么甚至可以把这个类从package-private改成 那"一个类"的private nested class
- 一般来说,某个class所有的成员一开始都必须是private的(只有class内部可以 访问).只有当同一个package的另外的class需要使用这个的时候,才要把private去掉 从而使得它变成package-private. package-private和private都是"实现"的一部分, 只有在极其特殊的情况下(比如implements Serializable接口的情况下)才会泄露
- 对public class的成员来说从package-private到protected是一个非常大的accessibility 的提升,因为protected其实是对subclass的public, 对subclass来说,也就相当于 API了,而且还有一部分继承方面的承诺(所以应该尽量少用protected来保护成员)
- 对于method来说,有一个特殊的要求:
如果一个函数override了superclass的函数,那么,这个函数不能有一个更低级的access level. 只有这样才能保证继承类的功能不比基类差
- 为了测试方便是可以把public class的成员从private临时调整为package-private的, 但是不能进一步调成了.因为private和package-private说白了,还是implementation
- 如果一个'成员变量'是非final的(或者final reference指向一个mutable object),
那么这个'成员变量'一定不能是public的: 因为一旦设置为public,你的'成员变量'
会被肆意改变,你也无能为力. 而且你的class肯定不是'thread safe'的了,因为
Classes with public mutable fields are not thread-safe
- 同样需要注意的是static的域,也是必须不能public的. 只有一个例外 (通常来说,这些域都是'全部大写'的)
static final fields contains either primitive values or references to immutable objects
- 需要特别注意的是nonzero-length array始终是mutable的
It is wrong for a class to have a public static final array field, or an accessor that returns such a field
- 上面提到了,即便把array设置成了private,但是IDE自动生成的accessor的话,会直
接返回private的array ref,这是不对的,正常的写法应该如下
private static final Thing[] PRIVATE_VALUES = { /*...*/ }; public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES)); //OR public static final Thing[] values() { return PRIVATE_VALUES.clone(); }
- 除了上面的static final 指向primitive或者immutable,public class不能有任何其他的成员变量是public的
- 尽可能的使每个类或者成员不被外界访问:
Item 14: In public classes, use accessor methods, not public fields
- 有时候,你会有如下定义的class,只有public field,没有函数
// Degenerate classes like this should not be public! class Point { public double x; public double y; }
- 有些强硬派的OO设计者,认为上面的class"必须"如下实现
// Encapsulation of data by accessor methods and mutators class Point { private double x; private double y; public Point(double x, double y) { this.x = x; this.y = y; } public double getX() { return x; } public double getY() { return y; } public void setX(double x) { this.x = x; } public void setY(double y) { this.y = y; } }
- 注意!上面的做法如果class是public的话,那么是正确的,如果不public的(private 或者package-private)的话,这么做是没必要的!因为private或者package-private 本来就是内部实现的一部分,而不是public API的一部分.
- 虽然把public class里面的一些field变成public的是不受欢迎的举动,但是如果这些
field是immutable的话,那么危害性会小一点
// Public class with exposed immutable fields - questionable public final class Time { private static final int HOURS_PER_DAY = 24; private static final int MINUTES_PER_HOUR = 60; public final int hour; public final int minute; }
Item 15: Minimize mutability
- 所谓immutable class就是:它的instance不能被更改的class.
- immutable class内部所有的信息在创建的时候就已经具有了,并且终生不会改变
- Java内部的几个著名immutable class:
- String
- boxed primitive classes
- BigInteger
- BigDecimal
- 为了能够让class变得immutable,必须遵循以下几点的要求:
- 不要提供任何修改对象state的函数
- 不要让class可以被继承(通常是声明class为final)
- 让所有的field都变成final的
- 让所有的field都变成private的
- 保证对所有内部mutable成员的"独占性"的访问.
- 实现了immutable以后,有如下好处:
- Immutable object are simple: immutable 对象的状态在一开始就固定了么,你可以 放心的把他们的state作为固定的条件
- Immutable objects are inherently thread-safe; they reqire no synchronization: 当多个线程访问它们的时候, 它们的数据不会被破坏, 所以immutable object是天然的 thread-safe, 天然的易于在thread之间分享. 所以对于immutable object来说,工厂 模式非常流行:因为同一个'值',我只需要一个对象就可以了(BigInteger就有这样的工厂).
- Immutable object还可以分享内部的成员变量
- Immutable object make great building blocks for other objects:一个典型的例 子是: Immutable object是Map中Key,以及Set中的element
- immutable唯一的缺点是:创建一个比较大的不可变类的代价比较高. 比如一个BigInteger
的instance, 我们要更改其中一个bit,那么因为BigInteger是immutable的,我们唯一的
办法就是再申请一块这么大的内存,然后改变其中一个bit
BigInteger moby = ...; moby = moby.flipBit(0);
- 但,并不是所有的类都有那么好的运气,能够成为immutable的class.如果不能设计成
immutable class,至少也要"尽可能的降低可变现":
除非有令人信服的理由要使得域变成非final的,否则要使某个每个域都是final的
Item 16: Favor composition over inheritance
- 这里的继承专指extends(而不是implements)
- 继承,封装,多肽是OO编程的三个特性,但是真实的情况是,多数情况下继承并不适用,
有限的几个适用继承的情况是(在java范围内):
- 在包内部使用继承(子类和父类都在同一个程序员控制之下)
- 设计转门被用来继承的类(并且文档全面)
- 可能与你的认识不同的是"继承其实打破了封装性": 因为子类的实现一来于父类的特定 代码, 而父类的代码如果在新版本中发生了改变,子类可能会遭到破坏(即便子类没有做 改变)
- 所以一个不是专门设计用来"extends"的class,最好不要去extends它,下面是一个例子
// Broken - Inappropriate use of inheritance! public class InstrumentedHashSet<E> extends HashSet<E> { // The number of attempted element insertions private int addCount = 0; public InstrumentedHashSet() { } public InstrumentedHashSet(int initCap, float loadFactor) { super(initCap, loadFactor); } @Override public boolean add(E e) { addCount++; return super.add(e); } @Override public boolean addAll(Collection<? extends E> c) { addCount += c.size(); return super.addAll(c); } public int getAddCount() { return addCount; } }
- 这个例子中我们创建了一个新的class(继承于HashSet), 除了继承HashSet的全部功能
外,我们还提供了一点功能上的改进:我们可以O(1)时间就求出总共增加了多少个成员.
但是当我们按照设想进行试验的时候,发现结果比预想值大了一倍(预想结果是3)
package org.hfeng.misc.ej2.item16; import java.util.Arrays; public class Test { public static void main(String[] args) { InstrumentedHashSet<String> s = new InstrumentedHashSet<String>(); s.addAll(Arrays.asList("Snap", "Crackle", "Pop")); System.out.println(s.getAddCount()); } } //////////////////////////////////////////////////// // <===================OUTPUT===================> // // 6 // ////////////////////////////////////////////////////
Item 17: Design and document for inheritane or else prohibit it
- 要想继承某个类,这个类的文档就必须精确的描述覆盖(override)每个方法所带来的影响:
这个要求的范围是:
- 可覆盖的,也就是非final的
- 公开的,也就是public(或者protected的)
- 什么情况下会调用上面的'可被覆盖'的函数.
Item 18: Perfer interfaces to abstract classes
Item 19: Use interfaces only to define types
- 当一个classs实现一个interface的时候,这个interface就可以当做这个class的instance 的类型(type). 也就是说这种instance因为实现了某个interface,所以具有了某种能力. 只能因为这个目的来实现接口,为其他目的实现接口的做法都是错误的
- 有种接口叫做常量接口(constant interface),接口里面没有函数,只有常量
// Constant interface antipattern - do not use ! public interface PhysicalConstants { static final double ELECTRON_MASS = 9.1093; static final double BOLTZMANN_CONSTANT = 1.38065; }
- 常量接口是对接口的不良使用:
- 首先常量是实现细节,不应该暴露出来
- 如果在将来的发型版中不再需要这些常量了,那么依然需要实现这个接口,以确保二 进制兼容性.
Item 20: Prefer class hierarchies to tagged classes
Item 21: Use function objects to represent strategies
Item 22: Favor static member classes over nonstatic
- nested class是一种定义在其他类里面的类
- nested class应该只服务于包裹它的类(enclosing class),否则nested 应该被声明为 top-level class
- nested class一共有四种:
- static member class:是最简单的nested class
- nonstatic member classes
- anonymous classes
- local classes
Chapter 5: Generics
Item 23: Don't use raw types in new code
- 声明中带有一个或者多个类(或者接口)来作为类型参数(type parameter)的情况就是generic, 比如
private final Collection<Stamp> stamps = ...;
- 这是1.5才引入的特性,原来的时候,所有的对象都是以Object的形式存入容器的.所以取 出来的时候,还要转义
Item 24: Eliminate unchecked warnings
- 在使用范型编码的时候,会遇到一些警告,比如
Set<Lark> exaltation = new HashSet();
- 如果你编译的话,会出现如下错误
Venery.java:4: warning: [unchecked] unchecked conversion
- 正确改正方法
Set<Lark> exaltation = new HashSet<Lark>();