Creating and Destroying Objects
# 2. Creating and Destroying Objects
# Item 1: Consider static factory methods instead of constructors
编写为一个 class 实例化 instance 的方式通常有两种:
- 提供一个 public constructor
- 提供一个 public static factory method
两者各有优缺点,根据不同的场景,选择一个合适的实例化方法。
# Item 2: Consider a builder when faced with many constructor parameters
静态工厂和构造器都无法很好地适应存在大量可选参数的情况。这一节介绍了三种用来生成这种对象的模式,并进行比较,体现了 Builder 模式在这种情况下的优点。
三种用来构建存在大量可选参数的模式:
- telescoping constructor pattern:先写一个有所有必选参数的 ctor,然后写一个带所有参数和一个可选参数的 ctor,再写一个带所有参数和两个可选参数的 ctor,....,直至在一个 ctor 里有所有的可选参数。
- JavaBeans pattern:ctor 是无参的,调用 ctor 后再通过各 setter 方法来设置各个成员变量。
- Builder pattern:在 class 内写一个 Builder 类,并通过它的
build()
方法来生成我们需要的实例。
Builder pattern 对于调用者来说可读性更好,并很好地与类的继承体系进行融合,如何编写出 Builder 可以参考原文。
这部分原文讲的很好,并涉及了如何编写类的泛型。
# Item 3: Enforce the singleton property with a private constructor or an enum type
这节主要介绍了在实现 singleton 的常见方法和注意事项。
两种常见方式:
- 方式 1:有一个
public static final
的属性 INSTANCE,并通过自身的私有构造器来初始化为单例。 - 方式 2:有一个
private static final
的属性 INSTANCE 并被私有构造器初始化,然后写一个静态工厂getInstance()
用来获得这个单例。
其实还经常使用“双重检查锁”的方法来实现单例。
实现单例的注意点:
- 使用者也许会通过反射来获取私有构造器进而破坏单例。
- 预防方法:让 ctor 在第二次被调用时抛出异常
- 对 singleton 序列化再反序列化后,可以获得新的实例,从而破坏了单例
- 预防方法:修改
readResolve()
方法,让其也直接返回已有的单例
- 预防方法:修改
之后原文重点介绍了通过声明 a single-element enum 的方式来实现单例:
public enum Elvis {
INSTANCE;
public void customMethod() {
...
}
}
2
3
4
5
6
7
这种方式可以抵御因序列化、反射而导致的破坏单例的攻击,但无法让你的 class 去继承一个非 Enum 的 superclass。如果可以,a single-element enum type is often the best way to implement a singleton。
# Item 4: Enforce noninstantiability with a private constructor
往往我们会写一些 utility classes,这种 class 里面是一堆 static methods 和 static fields,他们不被设计为可以实例化的。
然而 Java 编译器会默认为 class 生成 public、parameterless 的 ctor,为了防止有人将其实例化,我们可以为这种类编写一个显式的 private 无参构造器:
// Noninstantiable utility class
public class UtilityClass {
// Suppress default constructor for noninstantiability
private UtilityClass() {
throw new AssertionError();
}
... // Remainder omitted
}
2
3
4
5
6
7
8
这样就不会有人能够实例化或继承这个类了。
# Item 5: Prefer dependency injection to hardwiring resources
依赖注入就是在创建这个类时将它所依赖的其他类传递(pass)进去。区别于依赖注入的方式是这个类在内部把自己依赖的其他类给创建出来,而不是通过外界传入进去的方式。
最简单的注入方法就是在构造器方法中声明它所依赖的类,并在调用构造器实例化时完成依赖的注入。如下:
public class SpellChecker {
private final Lexion dictionary;
public SpellChecker(Lexicon dictionary) {
this.dictionary = Objects.requireNonNull(dictionary);
}
}
2
3
4
5
6
7
这里的 dependency injection 不要局限于 Spring 的依赖注入。
# Item 6: Avoid creating unnecessary objects
复用一个 single object 是比每次创建一个新的 functionally equivalent object 更好的。
- static factory methods 能帮助我们避免创建不必要的实例。
- 很多你看不见的地方会偷偷创建实例,比如 String 的
match()
方法内部会每次调用时创建一个 Pattern 实例用于正则匹配,而且用一次后就扔。 - 为了节省一两个对象的创建开销而使用惰性初始化技术是得不偿失的。
- 不可变类是可以安全地复用的。
- 为了避免 object 的创建而维护一个你自己的 object pool 是得不偿失的。
# Item 7: Eliminate obsolete object references
Null out references once they become obsolete.
但是,Nulling out object references should be the exception rather than the norm. 消除引用的最好方式是让引用这个 object 的变量自然地 fall out of scope。
Cache 的维护容易出现内存泄漏。如果你的 cache entry 的生命周期取决于 key 的外部引用,而不取决于 value,那使用 WeakHashMap 来实现 cache 是一个很好地选择。
# Item 8: Avoid finalizers and cleaners
finalizer 和 cleaner 都不是啥好东西,如果不是很了解他们的行为,那都不要用。
如果你需要在实例使用完后关闭一些资源,实现 AutoCloseable 是更好的选择。
# Item 9: Prefer try-with-resources to try-finally
关闭资源的最好编码风格是 try-with-resources 的方式,尤其是需要关闭多个资源时。
// try-with-resources on multiple resources - short and sweet
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)) >= 0)
out.write(buf, 0, n);
}
}
2
3
4
5
6
7
8
9
10