创建和销毁对象

考虑用静态工厂方法代替构造器

静态工厂方法是指静态的返回类实例的公共方法。它并不直接对应于设计模式中的工厂模式。
例如:

1
2
3
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}

与构造器相比,静态工厂方法有以下几个优点:

  1. 静态工厂方法可以有名字
  2. 不必在每次调用的时候都创建一个新对象
  3. 可以返回原返回类型的任意子类型对象

对于第三点,在编写包含该静态工厂方法的类时,静态工厂方法返回对象所属的类甚至可以不必存在。这种灵活的静态工厂方法构成了**服务提供者框架(service provider frameworks)**的基础,例如JDBC。服务提供者框架是指这样一个系统:多个服务提供者实现一个服务,系统确保这些实现对客户端可用,使得这些实现与客户端解耦。
服务提供者框架有三个重要组件以及一个可选的组件:

  • 服务接口(service interface):由服务提供者实现
  • 提供者注册API(provider registration API):系统用来注册实现,使得客户端可以访问它们
  • 服务访问API(service access API):客户端用来获取服务实例
  • 服务提供者接口(service provider interface):由服务提供者实现,用以创建他们自己的实例

例子:

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
// Service interface
public interface Service {
... // Service-specific methods go here
}

// Service provider interface
public interface Provider {
Service newService();
}

// Noninstantiable class for service registration and access
public class Services {
private Services() { } // Prevents instantiation

// Maps service names to services
private static final Map<String, Provider> providers = new ConcurrentHashMap<String, Provider>();
public static final String DEFAULT_PROVIDER_NAME = "<def>";

// Provider registration API
public static void registerDefaultProvider(Provider p) {
registerProvider(DEFAULT_PROVIDER_NAME, p);
}
public static void registerProvider(String name, Provider p){
providers.put(name, p);
}

// Service access API
public static Service newInstance() {
return newInstance(DEFAULT_PROVIDER_NAME);
}
public static Service newInstance(String name) {
Provider p = providers.get(name);
if (p == null)
throw new IllegalArgumentException("No provider registered with name: " + name);
return p.newService();
}
}

通过私有化构造器强化不可实例化的能力

有时,可能需要编写只包含静态方法和静态域的类。例如,工具类。私有化构造器以强化不可实例化的能力。

避免创建不必要的对象

要优先使用基本类型而不是装箱的基本类型,小心无意识地自动装箱:

1
2
3
4
5
6
7
8
// Hideously slow program! Can you spot the object creation?
public static void main(String[] args) {
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum);
}

这段代码在功能上是正确的,但是会比最佳实现慢一些,因为写错了一个字母。变量sum被声明为 Long 而不是 long,这意味着这段程序构造231个不必要的Long对象。

消除过期的对象引用

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
// Can you spot the "memory leak"?
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;

public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}

public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}

public Object pop() {
if (size == 0)
throw new EmptyStackException();
return elements[--size];
}

/**
* Ensure space for at least one more element, roughly
* doubling the capacity each time the array needs to grow.
*/
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}

这段程序没有明显的错误,无论如何测试都可以成功通过。但是随着逐渐增长的GC活动或者内存占用,会造成内存泄露,以至于降低性能。在极端情况下,这种内存泄露会导致磁盘交换(disk paging),甚至程序会因为OutOfMemoryError挂掉,但这种情况很少见。
当栈先增长然后收缩,那么从栈中弹出来的对象将不会被垃圾回收,即使程序不会再引用它们。这是因为栈维持着他们的过期引用(obsolete references)。所谓过期引用是指永远不会再被解除的引用。这类问题的修复方法很简单:

1
2
3
4
5
6
7
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}

当程序员第一次遇见这总问题后,往往会变得异常小心。对每一个对象的引用,一旦程序不再用到它就把它置为null。其实这样做是没有必要的,而且写出来的代码也很丑陋。清空对象引用应该是一种例外,而不是一种规范行为。(Nulling out object references should be the exception rather than the norm)。消除过期引用最好的方法是令持有过期引用的变量结束其生命周期(fall out of scope)。
那么,何时应该将一个引用置为null呢?当一个类管理自己的内存,程序员应该警惕内存泄露。
内存泄露另外一些常见的来源是缓存、监听器和回调。缓存需要看具体的情况。而监听器和回调,当实现一个API,客户端在这个API中进行注册时,却没有显式地取消注册,那么除非你采取某些动作,否则他们会聚集在一起。最好的确保回调被立即GC的方法是仅保存弱引用(weak references),比如仅将他们保存在WeakHashMap的键中。