EFFECTIVE JAVA 3RD EDITION — 第二章 创建和销毁对象

/ dev

Item 1 : using static factory method instead of constructor

多使用静态工厂方法而不是构造器

优势:

不足:

常用静态工厂方法命名:

  1. from 单参数的类型转换
  2. of 多参数聚合转换
  3. valueOf from和of更详细的转换
  4. instance or getInstance 有可能返回相同的实例
  5. create or newInstance 返回新实例
  6. getType 同getInstance,如果工厂方法定义在另一个类中
  7. newType 同上
  8. type 同上

Item 2 : considering a builder when faced with many constructor parameters

构造器参数过多时考虑使用builder模式

builder模式在Java 9中其实使用非常多,比如介绍 Java9 HTTP2新特性 里面HttpClient HttpRequest等等的实例化方式都是builder模式。

静态工厂方法和构造器在面对大量可选参数时候的扩展性非常不好:

比如你可以像下面一样实现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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public class Human {

private String name;

private Integer id;

private char sex;

private Date birthday;

private Double height;

private Double weight;

private String motto;

public static class Builder {
private String name;

private Integer id;

private char sex;

private Date birthday;

private Double height;

private Double weight;

private String motto;

public Builder name(String name) {
this.name = name;
return this;
}
public Builder id(Integer id) {
this.id = id;
return this;
}
public Builder sex(char sex) {
this.sex = sex;
return this;
}
public Builder birthday(Date birthday) {
this.birthday = birthday;
return this;
}
public Builder height(Double height) {
this.height = height;
return this;
}
public Builder weight(Double weight) {
this.weight = weight;
return this;
}
public Builder motto(String motto) {
this.motto = motto;
return this;
}
public Human build() {
return new Human(this);
}
}
private Human(Builder builder) {
name = builder.name;
id = builder.id;
sex = builder.sex;
birthday = builder.birthday;
height = builder.height;
weight = builder.weight;
motto = builder.motto;
}
}

构造一个Human:

1
2
3
Human.Builder builder = new Human.Builder();
builder.id(1).name("Tom").birthday(new Date()).height(1.8).weight(60.0).motto("Hello World");
builder.build();

builder模式的优缺点:

  1. 更加灵活,安全,对参数可以做更多的处理,调用代码可读性好
  2. 在性能要求极端的场景下builder创建所消耗的性能是不被容忍的
  3. 从传统的get,set或者构造方法迁移的时候代码量大(删除旧代码),所以最好一开始就使用builder模式

使用场景:

Item 3 : making singleton property private constructor or enum type

用枚举代替单例属性或者给它提供一个私有构造方法

构造一个单例:

1
2
3
Constructor constructor = Class.forName("me.minei.effective.SingletonEnforce").getDeclaredConstructor();
constructor.setAccessible(true);
constructor.newInstance();

这种情况在私有构造方法里判断是否实例化过,已经实例化抛出异常即可。

声明一个单元素枚举,通常这是最好的方法来实现单例,如果要扩展一个非枚举子类的话就不用这种方式实现:

1
2
3
public enum SingletonEnum {
INSTANCE;
}

使用前两种方式实现的单例如果需要序列化的话不光需要实现 Serializable 接口,同时所有实例属性都需要加 transient 关键字和提供一个私有的 readResolve 方法:

1
2
3
private Object readResolve() {
return INSTANCE;
}

否则每次反序列化时都会有新的实例创建。

Item 4 : Enforce noninstantiability with a private constructor

给不能实例化对象强制实现一个私有构造方法

对于不需要实例化的类提供一个私有构造器(里面抛出异常等等处理)来避免被实例化,通常是对于一些接口,使接口抽象是不可取的因为可以被继承然后实例化。

这样做不好的一点就是无法被继承。

Item 5 : Prefer dependency injection to hardwiring resources

优先依赖注入底层资源

静态聚合工具类(接口)和单例不适合依赖参数实例化的场景,简单说就是不要用单例或者是静态聚合类(接口)去实现一个依赖除自身的类,
并且不要用类直接去创建这些资源,传递给构造器(builder工厂方法)去处理,提升类的灵活性、可重用性和可测试性。

这种情况下需要将参数传递给构造器来实例化(依赖注入模式)
这种方式的一个变种是传入一个 factory 给构造器,这个 factory 可以重复的返回对象实例(Factory Method pattern)

Java8中的 Supplier就是 factory 一个很好的代表。
传入构造器的最好是一个通配的类型以便于扩展:

1
Mosaic create(Supplier<? extends Tile> tileFactory) { ... }

Item 6 : Avoid creating unnecessary objects

避免创建不必要的对象

Item 7 : Eliminate obsolete object references

及时处理过期对象引用

缓存可能造成内存泄漏(无用对象没有及时被回收)

  1. 使用WeakHashMap
  2. 启用一个后台进程去清理过期缓存

监听器和其他回调可能导致内存泄露

  1. 可以使用 weak references 将其放入 WeakHashMap

内存泄露往往不是立刻发生,而是可能潜伏在系统多年,只有发生了才会体现出来,可以使用 http://goog-perftools.sourceforge.net/doc/heap_profiler.html 来帮助分析

Item 8 : Avoid finalizers and cleaners

避免使用 finalizer 和 cleaner

可以实现 AutoCloseable 来代替finalizer和cleaner,然后实现其 close 方法即可,每个实例不再使用调用该方法即可,一般使用 try-with-resources 来确保万一。
值得一提的是,这种类最好有一个字段来记录一个实例是否被 close ,如果不小在实例已经被 closed 之后再次调用,记得抛出 IllegalStateException 异常。

finalizers和cleaners两个正确的用法:

  1. 资源占用着忘记调用上面提到的 close 方法来释放资源,虽然也并不能保证 finalizer 和 cleaner及时执行,但总比什么都不做好,但也需要考虑性能的缺失是否值得
  2. 处理 native peers, 因为 native peer 不是普通的java对象,所以gc不知道它也无法回收它,而finalizers和cleaners适合来完成这个任务

总的来说不要使用 cleaners 或者不要使用 Java9之前的 cleaners;
不要使用 finalizers ,除非需要安全操作或者关闭重要资源,并且留意他们的不确定性和性能。

Item 9 : Prefer try-with-resources to try-finally

优先使用 try-with-resources 而不是 try-finally

Java中有很多的资源需要手动释放,执行 close 方法即可,比如 InputStream,BufferedReader 等等,他们都直接或者间接的实现了 AutoCloseable 接口。

使用需要释放的资源时尽可能用 try-with-resources ,可以提升代码可读性,更加简洁清楚,异常堆栈信息更有利于排查bug,比如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static final int BUFFER_SIZE = 16;
static void copy(String src, String dst) throws IOException {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) &gt;= 0)
out.write(buf, 0, n);
} finally {
out.close();
}
} finally {
in.close();
}
}
1
2
3
4
5
6
7
8
9
10
private static final int BUFFER_SIZE = 16;
static void copy(String src, String dst) throws IOException {
try (InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst)) {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) &gt;= 0) {
out.write(buf, 0, n);
}
}
}

可以明显看到使用try-with-resources 代码简洁很多,更加清楚。