创建和销毁对象
考虑用静态工厂方法代替构造器
静态工厂方法是指静态的返回类实例的公共方法。它并不直接对应于设计模式中的工厂模式。
例如:
1 | public static Boolean valueOf(boolean b) { |
与构造器相比,静态工厂方法有以下几个优点:
- 静态工厂方法可以有名字
- 不必在每次调用的时候都创建一个新对象
- 可以返回原返回类型的任意子类型对象
对于第三点,在编写包含该静态工厂方法的类时,静态工厂方法返回对象所属的类甚至可以不必存在。这种灵活的静态工厂方法构成了**服务提供者框架(service provider frameworks)**的基础,例如JDBC。服务提供者框架是指这样一个系统:多个服务提供者实现一个服务,系统确保这些实现对客户端可用,使得这些实现与客户端解耦。
服务提供者框架有三个重要组件以及一个可选的组件:
- 服务接口(service interface):由服务提供者实现
- 提供者注册API(provider registration API):系统用来注册实现,使得客户端可以访问它们
- 服务访问API(service access API):客户端用来获取服务实例
- 服务提供者接口(service provider interface):由服务提供者实现,用以创建他们自己的实例
例子:
1 | // Service interface |
通过私有化构造器强化不可实例化的能力
有时,可能需要编写只包含静态方法和静态域的类。例如,工具类。私有化构造器以强化不可实例化的能力。
避免创建不必要的对象
要优先使用基本类型而不是装箱的基本类型,小心无意识地自动装箱:
1 | // Hideously slow program! Can you spot the object creation? |
这段代码在功能上是正确的,但是会比最佳实现慢一些,因为写错了一个字母。变量sum被声明为 Long 而不是 long,这意味着这段程序构造231个不必要的Long对象。
消除过期的对象引用
1 | // Can you spot the "memory leak"? |
这段程序没有明显的错误,无论如何测试都可以成功通过。但是随着逐渐增长的GC活动或者内存占用,会造成内存泄露,以至于降低性能。在极端情况下,这种内存泄露会导致磁盘交换(disk paging),甚至程序会因为OutOfMemoryError挂掉,但这种情况很少见。
当栈先增长然后收缩,那么从栈中弹出来的对象将不会被垃圾回收,即使程序不会再引用它们。这是因为栈维持着他们的过期引用(obsolete references)。所谓过期引用是指永远不会再被解除的引用。这类问题的修复方法很简单:
1 | public Object pop() { |
当程序员第一次遇见这总问题后,往往会变得异常小心。对每一个对象的引用,一旦程序不再用到它就把它置为null。其实这样做是没有必要的,而且写出来的代码也很丑陋。清空对象引用应该是一种例外,而不是一种规范行为。(Nulling out object references should be the exception rather than the norm)。消除过期引用最好的方法是令持有过期引用的变量结束其生命周期(fall out of scope)。
那么,何时应该将一个引用置为null呢?当一个类管理自己的内存,程序员应该警惕内存泄露。
内存泄露另外一些常见的来源是缓存、监听器和回调。缓存需要看具体的情况。而监听器和回调,当实现一个API,客户端在这个API中进行注册时,却没有显式地取消注册,那么除非你采取某些动作,否则他们会聚集在一起。最好的确保回调被立即GC的方法是仅保存弱引用(weak references),比如仅将他们保存在WeakHashMap的键中。