UP | HOME

effective-java-2nd

Table of Contents

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可以:
      1. 返回"函数返回类型"的"子类的object".
      2. 返回的object,其class是private的
    • 我们以Collection为例子来看看
      1. 首先, Collection是一个interface
        public interface Collection<E> extends Iterable<E>
        
      2. 其次, Collections是一个class,而且private ctor
        public class Collections {
            // Suppresses default constructor, ensuring non-instantiability.
            private Collections() {
            }
            // ....
        }
        
      3. 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));
        }
        
      4. 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;
            // ...
        }
        
    • 从上面的例子可以看出, static factory method设计出来的代码,用户能够知道的就 是返回值是一个接口(父类), 而不用去关心其具体到底底层是什么实现,这通常来说 都是良好的设计实践: 因为下一个版本,我甚至是可以更改底层的实现,但是对用户来 说没有影响,因为原来只承诺了接口
    • #TODO#
    • 优点4是:创建instance的时候,可以减少冗长的参数:
      1. 最开始是这样创建一个map
        Map<String, List<String>> m =
            new HashMap<String, List<String>>();
        
      2. 使用了static factory method后,可以简化成这样
        Map<String, List<String>> m = HashMap.newInstance();
        
      3. static factory method当然得如外提供啦
        public static <K, V> HashMap<K, V> newInstance() {
            return new HashMap<K, V>();
        }
        
  • 当然了,缺点也是存在的:
    • 缺点1: 如果只依靠static factory method的话,那么ctor就是private的,所以他们 就无法被实例化
    • 缺点2: static factory method和其他的static函数没有明显区别,所以用户想用来 创建instance的时候,往往很难寻找.下面是我们建议的几个名字:
      1. valueOf:返回一个和它参数一样的instance,通常用来类型转换
      2. of: 是valueOf的另外一种称呼
      3. getInstance: 如果有参数,那么就返回跟参数描述一致的instance. 如果是单例 模式(Singleton中), 那么就是没有参数,返回唯一存在的instance
      4. newInsstance: 和getInstance一样,不过保证返回的instance和其他的instance 完全不同
      5. getType: 返回值为Type类型的getInstance
      6. 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() {
              //...
          }
      }
      
  • 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勉强可以算作还有用的两个场景:
    1. 作为安全网:
      • 一个资源释放的晚总比不释放好,finalizer作为termination method以外的最后 一道屏障.事实上FileInputStream, FileOutputStream, Connection等都是有 try-finally block在里面再次调用termination method.
      • finalizer除了要起到"再次释放"资源的目的以外,还应该加一个log. 因为在finalizer 里面调用"释放资源"是不对的.所以应该记录异常事件,把这种情况作为一个bug. 很遗憾FileInputSream等的try-finally block代码里面没有这么做
    2. 释放没有重要资源的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":
    1. Each instance of the class is inherently unique: 比如Thread这种,其对象肯 定是实例,而不是value,那么唯一性是肯定的.Object里面的equals实现就够用了
    2. You don't care whether the class provides a "logical euqality" test: 比 如你可以为java.util.Random实现一个equals来对比两次实现的随机数是否一样. 但显然这个设计是多余的,没有人会用这个功能
    3. A superclass has already overridden equals, and the superclass behavior is appropriate for this class: 比如大多数Set的具体实现都继承于AbstractSet. List的实现继承于AbstractList, Map的实现继承于AbstractMap
    4. 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来说,如下的几点可以帮助我们更好的实现"封装"成员
    1. 尽可能的使每个类或者成员不被外界访问:
      • 对于top level的class或者interface来说, 只有两个选择package-private或者 是public. 如果能够选择package-private,那么毫不犹豫的选择package-private. 因为package-private使得你的代码是"实现"的一部分,而不是"API"的一部分,成 为API总是要付出责任的.
      • 如果某个类只是被"一个类"在内部使用,那么甚至可以把这个类从package-private改成 那"一个类"的private nested class
    2. 一般来说,某个class所有的成员一开始都必须是private的(只有class内部可以 访问).只有当同一个package的另外的class需要使用这个的时候,才要把private去掉 从而使得它变成package-private. package-private和private都是"实现"的一部分, 只有在极其特殊的情况下(比如implements Serializable接口的情况下)才会泄露
    3. 对public class的成员来说从package-private到protected是一个非常大的accessibility 的提升,因为protected其实是对subclass的public, 对subclass来说,也就相当于 API了,而且还有一部分继承方面的承诺(所以应该尽量少用protected来保护成员)
    4. 对于method来说,有一个特殊的要求:
                如果一个函数override了superclass的函数,那么,这个函数不能有一个更低级的access level. 只有这样才能保证继承类的功能不比基类差
      
    5. 为了测试方便是可以把public class的成员从private临时调整为package-private的, 但是不能进一步调成了.因为private和package-private说白了,还是implementation
    6. 如果一个'成员变量'是非final的(或者final reference指向一个mutable object), 那么这个'成员变量'一定不能是public的: 因为一旦设置为public,你的'成员变量' 会被肆意改变,你也无能为力. 而且你的class肯定不是'thread safe'的了,因为
      Classes with public mutable fields are not thread-safe
      
    7. 同样需要注意的是static的域,也是必须不能public的. 只有一个例外 (通常来说,这些域都是'全部大写'的)
      static final fields contains either primitive values or references to immutable objects
      
    8. 需要特别注意的是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
      
    9. 上面提到了,即便把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();
      }
      
    10. 除了上面的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,必须遵循以下几点的要求:
    1. 不要提供任何修改对象state的函数
    2. 不要让class可以被继承(通常是声明class为final)
    3. 让所有的field都变成final的
    4. 让所有的field都变成private的
    5. 保证对所有内部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>();
    

Item 25: Prefer lists to arrays

Item 26: Favor generic types