创建和销毁对象
§ 考虑使用静态工厂方法代替构造函数
优点:
- 有名字,可以更确切描述返回对象的名称。
- 不用每次调用都创建一个新的对象,可以重复调用返回 相同实例。
- 可以返回原返回类型的任何子类型对象。
- 返回对象的类可以根据输入参数的不同而不同
- 便携包含该方法的类时,返回的对象的类不需要存在。
- 可以在创建参数化类型实例的时候,使代码更简洁。
1 | public static final Boolean TRUE = new Boolean(true); |
缺点:
- 类如果不含公有的或受保护的构造器,就不能被子类化
- 它们与其他的静态方法实际没有区别,没有明确的标识,难以查找
以下是静态工厂方法常用名称:
- from__,类型转换方法,接受单个参数并返回此类型的相应实例。如
Date d = Date.from(instant)
- of__,聚合方法,接受多个参数返回该类型的实例,并合并在一起。如
Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING)
- valueOf__,from 与 to 更详细的替代方式
- instance 或 getInstance__,返回一个由其参数描述的实例
- create 或 newInstance__,保证每次调用返回一个新实例。
- getType__,与 getInstance 类似,但是在工程方法处于不同的类时候使用。getTyp 中的 Type 是工厂方法返回的对象类型。如:
FileStore fs = Files.getFileStore(path)
- newType__,与 newInstance 类似
- type__,getType 和 newType 简洁替代方法,如
List<Complaint> litany = Collections.list(legacyLitany)
§ 构造参数过多时要考虑使用 Builder 模式
类似于 lombok 中的 @Builder
注解
优点:
- 易于编写和阅读
- 可以对多个参数强加约束条件
缺点:
- 需要先创建它的构造器,会有一点点开销(虽然不明显)
1 | User user = User.builder() |
§ 用私有构造器或者枚举类型强化单例 Singleton 模式
经典的饿汉式单例模式,通过公有的静态工厂方法返回同一个对象的引用且 线程安全。
1 |
|
推荐单一元素的 枚举类型 是实现 Singleton 的最佳方法,无偿提供序列化机制,防止多次实例化。
1 | public enum Elvis { |
§ 通过私有构造器强化不可实例化的能力
当类不包含显示的构造器时,Java 编译器会自动提供一个公有的、无参的的构造器。
所以,当一些类(如工具类)不希望被实例化时,应当提供私有的构造器
1 | public class UtilityClass { |
AssertionError
可以避免不小心在类的内部调用构造器
§ 依赖注入优于硬连接资源
1 | public class SpellChecker { |
不要用单例和静态工具类来实现依赖一个或多个底层资源的类,且该资源的行为会影响到该类的行为;
也不要直接用这个类来创建这些资源,而应该将这些资源或者工厂传递给 构造器。通过它们来创建类。
这个就是 依赖注入。
依赖注入极大的提高了灵活性和可测试性,但可能使大型项目变得混乱,使用依赖注入框架(Dagger、Guice、Spring)可以消除这些混乱。
§ 避免创建不必要的对象
1 | // 极端的例子,每次执行都会创建一个新的 String 实例 |
通常可以使用提供静态工厂方法而不是构造器,避免创建不必要的对象。
优先使用基本类型而不是装箱基本类型,当心无意识的自动装箱。
1 | public static void main(String[] args) { |
如上,每次 sum 计算增加 long 会构造一个实例,应当避免。
创建正则表达式 Pattern 实例是昂贵的,需要将正则表达式编译成有限状态机。如果经常调用,可以将其作为类初始化的一部分。
1 | public class RomanNumerals { |
§ 消除过期的对象引用
过期的对象引用,是指永远也不会再被解除的引用,如下所示:
1 | public class Stack { |
栈先增长,再收缩,那么从 pop 弹出来的对象永远不会再被访问了,但它的引用仍然存在于数组中,将不会被当做垃圾回收,即使栈的程序不再引用这些对象。从而产生内存泄漏。
该例同时可参见《算法 第四版》1.3.2.4 对象游离。
解决方法是:
1 | public Object pop() { |
清空对象引用应该是一种例外,而不是一种规范行为,只要 Stack 实例结束生命周期(如在比较紧凑的作局部用域范围内),那么就会进行垃圾回收。
因为是 Stack 自己管理内存,存储池包含了 elements 数组,对于垃圾回收器而言,elements 数组中的所有对象引用都同等有效。
所以,只要是类自己管理内存,就应该警惕内存泄漏问题。
参考 JDK 中 ArrayList
中的示例:
1 | public E remove(int index) { |
§ 避免使用终结方法
终结方法,即finalize()
。Java 不仅不保证终结方法会被及时的执行,而且根本就不保证它们会被执行。
当一个程序终止的时候,某些已经无法访问的对象上的终结方法根本没有被执行,这是完全有可能的。如依赖终结方法来释放公共资源(数据库)上的永久锁,很容易让整个分布式系统垮掉。
System.gc
和 System.runFinalization
这两个方法也并不保证终结方法一定会被执行。
推荐的做法是提供一个显式终止的方法。并且通常与try-finally
结构结合使用,以确保及时终止。如 InputStream、OutputStream
上的 close 方法
1 | Foo foo = new Foo(); |
§ 使用 try-with-resources 代替 try-finally 语句
许多资源必须通过调用 close 方法手动关闭资源,try-finally 是可以保证资源正确关闭的最佳方式,即使程序抛出异常或返回的情况下。
但是当资源很多时候,需要多个 try-finally
语句,冗长混乱。
Java7 引入的 try-with-resources
语句得到了很好的解决。要使用这个构造,资源必须实现 AutoCloseable
接口。
1 | static String firstLineOfFile(String path) throws IOException { |
这样生成的代码更简洁,必要时还可以添加 catch
子句。