
Java从诞生到日趋完善,经过了不断发展日益强大,目前全世界拥有了成千上万的Java开发人员。如何编写出更清晰、更正确、更健壮且更易于重用的代码,是所有编程开发人员所追求的目标之一。
如果你现在已经在Java开发方面有了一定的经验,而且想更加深入地了解Java编程语言,成为一名更优秀、更高效的Java开发人员,那么,建议你用心研读本书。
Questions
- 何时以及如何创建对象?
- 何时以及如何避免创建对象?
- 如何确保它们能够适时地销毁?
- 如何管理对象销毁之前必须进行的各种清理动作?
第1条:考虑用静态工厂方法代替构造器
什么是静态工厂方法?
如下有一个Boolean类中的valueOf方法,这个方法将boolean原始类型转换成了一个Boolean对象的引用。
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
静态工厂方法与构造器不同的第一大优势在于,它们有名称。
如果构造器的参数本身没有确切地描述正被返回的对象,那么具有适当名称的静态工厂会更容易使用,产生的客户代码也更易阅读。
静态工厂方法与构造器不同的第二大优势在于,不必在每次调用它们的时候都创建一个新的对象。
静态工厂方法能够为重复的调用返回相同的对象,这样有助于类总能严格控制在某个时刻哪些实例应该存在。
这种类被称作实例受控的类(instance-controlled)。
实例受控的类可以确保它是一个Singleton或者是不可实例化的。
它还确保不会存在两个相等的实例,即当且仅当a==b时才有a.equals(b)为true。如果类保证了这一点,它的客户端就可以使用==操作符来代替equals()方法,这样可以提高性能。
枚举类型就保证了这一点。
静态工厂方法与构造器不同的第三大优势在于,它们可以返回原返回类型的任何子类型的对象。
这样我们在选择返回对象的类型时就有了更大的灵活性。
参考如下示例:
// Service interface
public interface Service {
...
}
// Service provider interface
public inteface 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 regiserDefaultProvider(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();
}
}
静态工厂方法与构造器不同的第四大优势在于,在创建参数化类型实例的时候,它们使代码变得更加简洁。
Map<String, List<String>> m =
new HashMap<String, List<String>>();
以上代码让人觉得很繁琐,假设HashMap提供了如下的静态工厂:
public static <K, V> HashMap<K, V> newInstance() {
return new HashMap<K, V>();
}
这样就用如下代码代替前面繁琐的声明:
Map<String, List<String>> m = HashMap.newInstance();
到目前为止jdk1.8,标准的集合实现如HashMap并没有工厂方法,但是你可以把这些方法放在自己的工具类中。更重要的是,可以把这样的静态工厂放在你自己的参数化类中。
但是在jdk1.7提供了简便的泛型声明,可以使用如下代码代替前面的声明:
Map<String, List<String>> m = new HashMap<>();
静态工厂方法与构造器不同的主要缺点在于,类如果不含共有的或者受保护的构造器,就不能被继承。
因为静态工厂方法一般都需要将构造器私有化,所以它鼓励使用复合(composition)而不是继承。
静态工厂方法与构造器不同的第二个缺点在于,它们与其它的静态方法实际上没有任何区别。
下面是静态工厂方法的一些惯用名称:
- valueOf
- of
- getInstance
- newInstance
- getType
- newType
总结
简言之,静态工厂方法和公有构造器都各有用处,我们需要理解它们各自的长处。静态工厂通常更加合适,因此切记第一反应就是提供公有的构造器,而不先考虑静态工厂。
第2条:遇到多个构造器参数时要考虑使用构建器
考虑一下用一个类表示包装食品外面显示的营养成分标签。这些标签中的几个域是必需的:每份的含量、每罐的含量以及每份的卡路里,还有超过20个可选域:总脂肪量、饱和脂肪量、转化脂肪、胆固醇、钠等等。大多数产品在某几个可选域中都会有非零的值。
以下采用向来习惯的重叠构造器(telescoping constructor)模式描述:
1 | // Telescoping constructor pattern - does not scale well(递增适应性)! |
当你想要创建实例的时候,就利用参数列表最短的的构造器,但该列表中包含了要设置的所有参数:
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
如果“仅仅”是这6个参数,看起来还不算太糟,问题是随着参数数目的增加,它很快就失去了控制。
一句话总结:重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然较难以阅读。
遇到许多构造器参数时,可以选用第二种方案——JavaBean模式,在这种模式下调用一个无参构造器来创建对象,然后调用setter方法来设置每个必要的参数,以及每个相关的可选参数:
1 | // JavaBean Pattern - allows inconsistency, mandates mutability |
这种模式弥补了重叠构造器模式的不足——在创建实例时很容易,这样的代码也易于阅读:
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
JavaBean模式自身具有的缺点:
1. 因为构造过程被分到几个调用中,在构造过程中JavaBean可能处于不一致状态。类无法仅仅通过检验构造器参数的有效性来保证一致性。试图使用处于不一致状态的对象,将会导致失败,这种失败与包含错误的代码大相径庭,因此它调试起来十分困难。
2. JavaBean模式阻止了把类做成不可变的可能,这就需要程序员付出额外的努力来确保它的线程安全。
幸运的是,还有第三种方案可供选择。既能保证像重叠构造器模式那样的安全性,也能保证像JavaBean模式那样的可读性。这就是Builder模式的一种形式。不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builder对象。然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后,客户端调用无参的build方法来生成不可变的对象。这个builder是它构建的类的静态成员类:
1 | // Builder Pattern |
客户端代码:
NutritionFacts cocaCola = new NutritionFacts.Builder(240,8).
calories(100).sodium(35).carbohydrate(27).build();
总结:如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是种不错的选择,特别是当大多数参数都是可选的时候。