这里是阅读《Effective Java中文版第二版》的读书笔记,这里会记录一些我的感受稍微有些重要的内容,方便之后查阅,可能会由于我的实力缘由致使理解有误,如有发现欢迎指出。一些我的还不理解的会用斜线标注。java
第一章是引言,因此跳过。设计模式
静态工厂方法是指一个返回类的实例的静态方法,例如:数组
public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean,FALSE; }
众所周知,构造器的方法名是必须和类名同样的,所以对于有多个参数类型相同的构造方法,一种方法是更改参数的顺序,另外一种是增长一个flag来判断执行哪一个构造方法。可是这样对于使用者是不友好的,他必须熟悉API或者查阅开发文档。假若使用静态工厂方法,那么能够经过方法名来给予使用者良好的提示与说明。框架
这句话的典型应用是在设计模式的单例模式中,静态工厂方法可以为重复的调用返回相同的对象。函数
构造方法是不能使用return语句的,它在使用时也只能产生自身这个类的一个对象,而静态工厂方法可使用return语句,所以在选择返回对象时就有了更大的灵活性。这个优点的应用不少,好比服务提供者框架模式。工具
应当熟悉静态工厂方法和构造器的各自的长处,在合适的场景使用合适的方法。性能
在面对一个拥有多个属性的类且构造方法拥有多个可选参数时,一个常见的方法是使用重叠构造器模式(建立多个构造方法,每一个构造方法比前一个构造方法有新的参数)。例如,第一个构造方法有两个必须参数,第二个构造方法有两个必须参数和一个可选参数,第三个构造方法有两个必须参数和两个可选参数,以此类推。可是当有许多参数的时候,代码会变得很难编写,也很难阅读,甚至会容易出错。测试
另外一个方法是使用javabean模式。由于构造过程被分到了多个调用中(为每一个属性的赋值调用该属性的set方法),在构造过程当中,javabean可能处于不一致的状态,这种问题难以发现。ui
第三种方法就是构建器模式(Builder模式)的一种形式。this
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 { // 必须属性 private final int servingSize; private final int servings; // 可选属性 private int calories = 0; private int fat = 0; private int sodium = 0; private int carbohydrate = 0; public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; } public Builder setCalories(int calories) { this.calories = calories; return this; } public Builder setFat(int fat) { this.fat = fat; return this; } public Builder setSodium(int sodium) { this.sodium = sodium; return this; } public Builder setCarbohydrate(int carbohydrate) { this.carbohydrate = carbohydrate; 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 n = new NutritionFacts.Builder(200,10).setCalories(20).setFat(30).build();
Builder模式十分灵活,能够利用一个builder来建立多个相同的对象,而且对必须参数和可变参数的实现符合人类的正常思惟。另外,对于使用者而言,使用时的代码更容易阅读和编写。
这种方法我在google的protobuf的java实现中见到过。
私有构造方法就不提了,这里记录一下第二个:
public enum A { INSTANCE; public void leaveTheBuilding() {...} }
对于一些只包含静态方法或者静态属性的类(好比工具类),咱们不但愿他们被实例化。众所周知,在缺乏显式构造方法的时候,编译器会默认添加一个无参的构造方法。若是为了严谨,咱们能够添加一个私有的构造方法,更能够在这个构造方法中throw异常来停止程序。
通常来讲,最好能重用对象而不是在每次须要的时候就建立一个相同功能的新对象。
除了重用不可变的对象以外,也能够重用那些已知不会被修改的可变对象。
能使用基本数据类型,就尽可能不要用对应的封装类。
不能觉得有了垃圾回收机制后,就不须要考虑内存管理的事情了。
例如用数组来实现栈,当实现出栈操做,size-1后,栈顶坐标后的元素对使用者来讲就已是无效部分了,可是数组仍然拥有对它们的引用,所以垃圾回收机制不会将它们回收。解决办法是在出栈时,将引用置空。
除了特定状况,不要使用终结方法(finalize)。
子类覆盖了父类的终结方法后,子类的终结方法不会自动调用父类的终结方法,须要手动调用。
约定的内容:
equals方法实现了等价关系。
实现高质量equals方法的诀窍:
一个简单的列子:
public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof MyClass)) return false; MyClass obj = (MyClass) o; return obj.field0 == this.field0 && obj.field1 == this.field1; }
告诫:
public boolean equals(MyClass o); // Don't do this!
若是没有共同覆盖equals方法和hashCode方法,那么该类将没法结合全部基于散列的集合一块儿正常运做,这样的集合包括HashMap、HashSet和HashTable。
约定:相等的对象必须具备相等的散列码(HashCode)。
在散列码的计算过程当中,必须排除equals比较计算中没有用到的任何字段,能够把冗余字段(它的值能够根据参与计算的其余字段计算出来)排除在外。
不要试图从散列码计算中排除掉一个对象的关键部分来提升性能。
提供好的toString实现可使类用起来更加温馨。
若是你继承了一个实现了Cloneable接口的类,那么你除了实现一个行为良好的clone方法外,没有别的选择。不然,最好提供某些其余的途径来代替对象拷贝,或者干脆不提供这样的功能。
另外一个实现对象拷贝的好方法是提供一个拷贝构造方法或者拷贝工厂。
// 拷贝构造方法 public MyClass(MyClass mc); // 拷贝工厂 public static MyClass newInstance(MyClass mc);
类实现了Comparable接口,就代表它的实例具备天然顺序关系(natural ordering)。
约定:(符号sgn(表达式)表示数学中的signum函数,根据表达式的值为负值、零和正值,分别返回-一、0和1)
sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
。(这也意味着,当且仅当y.compareTo(x)抛出异常时,x.compareTo(y)才必须抛出异常)x.compareTo(y) > 0 && y.compareTo(z) > 0
成立意味着x.compareTo(z) > 0
。x.compareTo(y) == 0
意味着全部的z都知足sgn(x.compareTo(z)) == sgn(y.compareTo(z))
。(x.compareTo(y) == 0) == (x.equals(y))
,但这绝非必要。若违反了这个条件,应当给予说明。比较浮点字段用Double.compare或者Float.compare。
若是一个类有多个关键字段,按照什么样的顺序来比较是很是重要的。
compareTo方法中,若是两个对应字段不相等,可使用该类的字段与传入参数的字段的差值做为返回值,但应确保差值是绝对正确的。