《Effective Java第三版》快读

之前已经拜读过第二版,本篇是对第三版的快读小记。

章节名:Greating and Destroying Objects(1~9)

获取实例可以考虑使用静态工厂方法

  1. 有名字;
  2. 不需要每次调用创建新对象;
  3. 能返回返回类型的任何子类对象;
  4. 根据输入参数的不同返回不同类的对象;
  5. 编写包含静态工厂方法的类时,返回对象的类不需要存在;
  6. 没有公共或受保护构造方法的类不能被子类化;
  7. 不易查找;

构造方法参数过多使用builder模式

  1. JavaBeans模式在多次调用构造过程中状态处于不一致,需要应对线程不安全;

builder模式示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int fat;
public static class Builder {
private final servingSize;
private final servings;
private int fat = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder fat(int val) {
fat = val;
return this;
}
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
fat = builder.fat;
}
}

调用的时候可以采用:

1
2
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.fat(50).build();

使用私有构造方法或者枚举实现Singleton

  1. 常规方法可以通过AccessibleObject.setAccessible进行反射攻击,静态工厂方法亦然;
  2. 静态工厂方法便于编写泛型单例工厂,方法引用可以使用supplier;
  3. 为了避免序列化实例被反序列化时创建新的实例,前两种方法需要将implements Serializable添加到声明中,所有实例属性声明为transient,提供readResolve方法;
  4. 用声明单一元素枚举类的方法实现单例可以规避前两种方法的一些弊端,通常是实现单例的最佳方式;
  5. 如果单例必须继承Enum以外的父类,就不要使用4的方法;

使用私有构造方法执行非实例化

使用私有构造方法来实现类的非实例化:

  1. 使用throw new AssertionError()保证类在任何情况下都不会被实例化;
  2. 添加注释Suppress default constructor for noninstantiability;

使用依赖注入取代硬件连接资源

  1. 如果一个类依赖一个或多个底层资源,且这些资源的行为会影响该类的行为,不要使用单例或者静态实用类来实现该类,而是采用依赖注入;

避免创建不必要的对象

  1. String s = “bikini”;
  2. 使用静态工厂方法,避免创建不需要的对象;
  3. 考虑一些对象创建的昂贵性,比如Java里正则表达式创建Pattern实例,可以使用private static final来缓存实例,书中提到了延迟初始化,但不建议,因为会导致实现复杂化,而性能没有可衡量的改进;
  4. 优先使用基本类型而非装箱类型,注意无意识的自动装箱;
  5. 本条目并非暗示避免创建对象,具体问题具体分析;

消除过期对象引用

  1. 消除过期引用的最好方法是让包含引用的变量超出范围;
  2. 需要实现的缓存满足缓存外对某个项的key引用,可以用WeakHashMap来表示该缓存;
  3. 常见的内存泄漏还来源于监听器和它的回调,一种解决方法是将它们保存在WeakHashMap的key中;

避免使用Finalizer和Cleaner机制

  1. Java 9中Cleaner机制代替了Finalizer机制;
  2. 除了作为安全网或者终止非关键的本地资源,不要使用Cleaner机制(或者java 9之前的Finalizer);

使用TWR语句代替try-finally语句

  1. 使用TWR替代try-finally语句,更简洁,更清晰,生成的异常更有用;
  2. 要使用TWR,资源必须实现AutoCloseable接口;

向外抛异常的示例:

1
2
3
4
5
6
static String firstLineOfFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(
new FileReader(path))) {
return br.readLine();
}
}

也可以配合catch:

1
2
3
4
5
6
7
8
9
static String firstLineOfFile(String path) {
try (BufferedReader br = new BufferedReader(
new FileReader(path))) {
return br.readLine();
// 如果不能打开或者读取文件,则返回默认值
} catch (IOException e) {
return defaltVal;
}
}

章节名:Methods Common to All Objects

重写equals方法时遵守通用约定

  1. 如果不覆盖equals方法,类的每个实例只与自身相等,Object的equals()实现:
1
2
3
public boolean equals(Object obj) {
return (this == obj);
}
  1. 重写equals()时,必须遵守通用约定:自反性,对称性、传递性、一致性,非空性(对于非空引用x,x.equals(null)必须返回false);
  2. 如何编写高质量的equals方法?书中提供了一些条例,其实可以配合String的equals方法来看,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
1. 首先使用`==`来检查是否为被比较对象引用,这是一种优化,特别是有些比较很昂贵;

2. 使用`instanceof`运算符检查参数是否具有正确类型(或接口);

3. 参数转换为正确类型(或接口);

4. 对需要比较的属性进行比较,比如上列代码中的字符串长度以及每个字符;
  1. 上文第三条的第四点中提到的比较,在不同的类型中会使用不同的比较形式,具体看书中给出的例子;
  2. equals()的参数Object不能替换成其它类型,因为这样做就不是重写的Object.equals方法,只是重载了方法,使用Override注解会阻止你犯这个错误;
  3. 除非必须,不要重写equals方法;

重写equals方法时也要重写hashcode方法

  1. 同一段代码在不同的应用程序中执行,hashCode方法返回的hashcode值始终一样;
  2. 两个对象使用equals方法比较返回为True,那么两个对象的hashcode值相同;
  3. 如果两个对象使用equals方法比较返回为False,那么两个对象的hashcode值未必不同,为不相等的对象生成不同hashcode值可能提高散列表的性能;

始终重写toString方法

  1. 除非父类已经这样做了,否则应该在每个实例化的类中重写Object的toString实现;
  2. 重写过程中,无论是否指定格式,应该在文档中表明意图;
  3. 在静态工具类编写toString无意义,也不应该在大多数枚举类型中写toString方法,但应该在抽象类中定义;

谨慎地重写clone方法

  1. if a class implements Cloneable, Object’s clone method returns a field-by-field copy of the object; otherwise it
    throws CloneNotSupportedException;
  2. clone方法规范内容(非绝对要求):

    1
    2
    3
    x.clone() != x // true
    x.clone().getClass() == x.getClass() // true
    x.clone().equals(x) // true
  3. 想要复制的对象是通过调用super.clone()获得,如果每一个类和它的父类(除了Object)都是这样,那么x.clone().getClass() == x.getClass()返回的就是true;

考虑实现Comparable接口

小结