Effective Java:Ch2_创建销毁对象:Item1_考虑用工厂方法替代构造函数
本章的主题是创建和销毁对象:何时创建、怎样创建;何时应该避免创建、如何避免创建;如何确保对象适时被销毁;如何管理对象销毁前的清理动作。
一个类如果要允许客户获得其实例,常用方法是提供一个public的构造函数。还有另外一个方法,也应该在每个程序员的工具集中占有一席之地:类可以提供一个public的静态工厂方法,这个方法返回类的实例。【例】下面是Boolean类(基本类型boolean对应的包装类)代码里的一个简单示例,该方法将一个boolean简单类型转换为一个Boolean对象引用。
public static Boolean valueOf(boolean b){ return b ? Boolean.TRUE : Boolean.FALSE; }注意这里讲的静态工厂方法与设计模式中的工厂方法模式是不一样的,它在设计模式中没有直接等价物。
类可以提供给它的客户静态工厂方法,用来替代构造方法,或作为构造方法的补充。用静态工厂方法来替代public构造方法既有优点也有缺点。
优点一:静态工厂方法不同于构造函数,他们有名称。如果构造函数的参数不能确切地描述正被返回对象,那么具有合适名称的静态工厂方法就更易用,对应的客户端代码页更加易于阅读。【例】例如,构造方法BigInteger(int, int, Random)返回一个可能为素数的BigInteger,而用一个名为BigInteger.probablePrime()的静态工厂方法就更好。(JDK1.4最终增加了这个方法。)
public class BigInteger extends Number implements Comparable<BigInteger> { public static BigInteger probablePrime(int bitLength, Random rnd) { if (bitLength < 2) throw new ArithmeticException("bitLength < 2"); // The cutoff of 95 was chosen empirically for best performance return (bitLength < SMALL_PRIME_THRESHOLD ? smallPrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd) : largePrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd)); } public BigInteger(int bitLength, int certainty, Random rnd) { BigInteger prime; if (bitLength < 2) throw new ArithmeticException("bitLength < 2"); // The cutoff of 95 was chosen empirically for best performance prime = (bitLength < 95 ? smallPrime(bitLength, certainty, rnd) : largePrime(bitLength, certainty, rnd)); signum = 1; mag = prime.mag; } }对于指定的方法签名,一个类只能有一个对应的构造函数。程序员通常这样来避开这个限制:可以提供两个构造函数,他们的参数列表仅仅是参数类型的顺序不同。这实在是一个坏主意。API的用户将不会记得这些构造函数哪个是哪个,常会无意地调用错误的构造函数。其他人读到使用这些构造函数的代码时,将不会知道这些代码是做什么的,除非参考类文档。
由于静态工厂方法有名称,所以没有上述限制。假如一个类需要方法签名相同的多个构造函数,就用静态工厂方法来替代构造函数,并仔细地选择方法名称以便突出它们之间的区别。
优点二:静态工厂方法不同于构造函数,无需每次被调用时都创建一个新对象。这使得非可变类(Item15)可以使用预先创建的实例,或者在创建实例时缓存起来,之后分发给客户,从而避免创建不必要的重复对象。Boolean.valueOf(boolean)方法演示了这种技术:它从不创建对象。这种技术类似于Flyweight模式。如果客户端经常请求创建相同的对象,那这种技术能极大的提高性能,尤其是当创建对象开销很大时。
静态工厂方法可以再反复调用时返回同一个对象,这使得类可以严格控制在哪个时刻有哪些实例存在。这种类又被称为实例受控的类(instance-controlled)。编写实例受控的类有这么几个理由:第一,实例受控允许一个类确保是单例的(Item3),或者是不可实例化的(Item4);同时,实例受控允许非可变类(Item15)确保不会存在两个相等的实例,即当且仅当a==b时才有a.equals(b),如果一个类做出了这种保证,则它的客户端可以用==操作符来地带equals(Object)方法,这可能会提高性能。枚举类型(Item30)就保证了这一点。
优点三:静态工厂方法不同于构造函数,它能返回任意子类型的对象。这让你在选择返回对象的类型时有了很大的灵活性。
这种灵活性的一个应用是,API可以返回一个对象,而无需使对象的类public。用这种方式隐藏实现类能够产生一个非常紧凑的API。这种技术适用于基于接口的框架(interface based frameworks, Item18),在这种框架中,接口成为静态工厂方法的自然返回类型。由于接口不能有静态方法,所以按照惯例,接口Type的静态工厂方法被放在一个名为Types的不可实例化类(noninstantiable
class, Item4)中。
【例】例如,Java集合框架中有32个集合接口的便利实现,提供不可修改的集合、同步集合等等。几乎所有的实现都通过一个不可实例化类(java.util.Collections)中的静态工厂方法导出,返回对象的类都是非public的。——Collections.unmodifiableMap(Map)方法的返回类型是UnmodifiableMap,是个private类!
public class Collections { public static <K,V> Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m) { return new UnmodifiableMap<K,V>(m); } private static class UnmodifiableMap<K,V> implements Map<K,V>, Serializable { } }如果把上述便利实现都作为public类导出,那集合框架API会臃肿很多。而且这并不仅仅是API数量的减少,还有conceptual weight的减轻。用户知道返回的对象都是由API通过接口精确定义了的,所以无需为实现类阅读额外的类文档。此外,使用静态工厂方法后,要求客户端通过接口来引用返回对象,而不是通过实现类来引用返回对象,这通常是一个好习惯。
public静态工厂方法返回的对象类型不仅是可以非public,而且还能根据参数的不同,而返回不同的类型。只要是所声明的返回类型的任何子类型,都是允许的。为了提高软件可维护性和性能,返回类型还可以随着版本的不同而不同。
【例】JDK1.5版本引入的java.util.EnumSet类没有public构造函数,只有静态工厂方法。根据底层枚举类型的大小,这些工厂方法可以返回两种实现:如果拥有64个或更少的元素(大多数枚举类型都是这样),静态工厂方法返回一个RegularEnumSet实例,用单个long来支持;如果枚举类型拥有65个或更多的元素,静态工厂方法则返回JumboEnumSet实例,用long数组来支持。
这两种实现类的存在对客户端是不可见的。如果RegularEnumSet对于小的枚举类型提供性能优势,那么就能在以后的版本中删掉,并不产生副作用。同样,如果可以提高性能,以后版本可以增加EnumSet的第三或第四个实现。客户端不知道也不关心静态工厂返回的对象的类型,客户端只关心它是EnumSet的某个子类。
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E> implements Cloneable, java.io.Serializable { public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) { Enum[] universe = getUniverse(elementType); if (universe == null) throw new ClassCastException(elementType + " not an enum"); if (universe.length <= 64) return new RegularEnumSet<E>(elementType, universe); else return new JumboEnumSet<E>(elementType, universe); } } class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> { } class JumboEnumSet<E extends Enum<E>> extends EnumSet<E> { }
在编写静态工厂方法时,其返回的对象所属的类甚至可以不存在。静态工厂方法的这种灵活性形成了服务提供者框架(service provider frameworks)的基础,例如Java数据库连接API(JDBC)。所谓服务提供者框架是这样一个系统:在这个系统中多个服务提供者实现一个服务,系统让这些实现对客户端可用,使他们与实现解耦。
服务提供者框架中有三个重要组件:服务接口(service interface),它负责提供实现;提供者注册API(provider registration API),系统用它来注册实现,让客户端访问实现;服务访问API(service access API),客户端用它来获取服务的一个实例,它一般允许客户端指定选择提供者的条件,但并不强制要求。如果不指定,则API返回一个默认实现的实例。服务访问API是“灵活的静态工厂”,形成了服务提供者框架的基础。
服务提供者框架还有第四个可选组件:服务提供者接口(service provider interface),提供者实现它用来创建其服务实现的实例。如果没有服务提供者接口,实现则是通过类名来注册,并通过反射实例化(Item53)。【例】以JDBC为例,Connection就是服务接口(service interface);DriverManager.registerDriver是提供者注册API(provider
registration API);DriverManager.getConnection是服务访问API(service access API);Driver是服务提供者接口(service provider interface)。
服务提供者框架有多种变体。例如服务访问API可以通过适配器模式,返回比提供者要求的更加丰富的服务接口。【例】下面是一个简单示例,有一个服务提供者接口,和一个默认提供者:
//Service provider framework sketch //--1)Service interface public interface Service{ //service-specific methods go here } //--4)Service provider interface public interface Provider{ Service newService(); } //Noninstantiable class for service registration and access public class Services{ private Services(){} //pervents 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>"; //--2)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); } //--3)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(); } return p.newService(); } }
优点四:静态工厂方法创建参数化类型实例时更加简洁。如果你调用参数化类的构造函数,那么很不幸,你必须要指定类型参数,即便上下文中已明确了类型参数。这通常要求你连续两次提供类型参数:
Map<String, List<String>> m = new HashMap<String, List<String>>();
随着类型参数长度和复杂性的不断增长,这种冗长的规范很快变得痛苦起来。然而,使用静态工厂的话,编译器能够替你计算出类型参数。这就是类型推导(type inference)。【例】例如,假设HashMap提供了如下静态工厂:
public static <K, V> HashMap<K, V> newInstance(){ return new HashMap<K, V>(); }
然后你就可以讲上文冗长的声明替换为如下这种简洁的形式:
Map<String, List<String>> m = HashMap.newInstance();也许有一天Java可以再构造函数调用和方法调用时进行这种类型推导,不过截至JDK1.6还不能做到。
遗憾的是,JDK1.6版本的标准集合实现(如HashMap)中并没有工厂方法,不过你可以再自己的工具类中加入这些方法。更重要的是,你可以再自己的参数类类中提供这种静态工厂。
缺点一:只提供静态工厂方法的主要缺点是,没有public或protected构造函数的类不能被子类化。public静态工厂返回的非public类也同样如此。例如,你不能子类化集合框架中便利实现类。这样也好,它鼓励程序员使用复合,而不是继承(Item16)。
缺点二:静态工厂方法不容易与其他静态方法相区分。它们并不像构造函数在API文档中那样突出,所以如果类提供静态工厂方法而不是构造函数,要找出如何实例化该类就比较困难了。也许某一天Javadoc工具会注意到静态工厂方法。现阶段,你可以通过在类或接口注释中标注静态工厂,并遵守通用的命名习惯,来减少这一缺陷。静态工厂方法的一些常用命名如下:
valueOf——不严格地讲,该方法返回一个与其方法参数具有相同值的实例。这种静态工厂实际上是类型转换方法。
of——valueOf的简洁形式,EnumSet(Item32)使之流行起来。
getInstance——返回方法参数所描述的一个实例,不过不一定与方法参数具有相同值。在单例模式中,getInstance没有参数,返回唯一实例。
newInstance——与getInstance类似,不过newInstance确保返回的每个实例都是不同的。
getType——与getInstance类似,只不过当工厂方法在另外一个不同的类中的时候使用。Type表示工厂方法返回对象的类型。
newType——与newInstance类似,只不过当工厂方法在另外一个不同的类中的时候使用。Type表示工厂方法返回对象的类型。
总之,静态工厂方法和public构造方法各有用处,我们需要理解他们各自的长处。通常静态工厂更可取,所以第一反应应该是考虑使用静态工厂,而不是public构造函数。
相关推荐
4. **构造器**:书中阐述了如何设计和使用构造器来创建对象,强调了单参数构造器和工厂方法的优缺点,以及如何利用Builder模式来构建复杂对象。 5. **异常处理**:异常处理是Java编程中的一个重要方面,Bloch建议...
1. **静态工厂方法**:相比构造器,静态工厂方法有优点如可以不返回新实例、允许返回同一实例(单例)、可以有更具选择性的访问控制,并且命名更自由。 2. **构建器(Builder pattern)**:当类有多个构造器参数时,...
- 工厂方法:介绍工厂方法模式,作为创建对象的抽象接口,提供更大的灵活性。 - 可枚举类型(enum):推荐使用枚举代替常量类,因为枚举具有内置的单例特性且更安全。 2. **第4章 类和接口** - 抽象类与接口:...
Effective Java是一本关于Java编程语言的经典书籍,本笔记主要总结了Java语言的发展历程、静态工厂方法的应用、构造器模式的使用等重要知识点。 一、Java语言的发展历程 Java语言的发展可追溯到1991年,当时由...
文档的内容部分开始讲述了Java编程中的一个关键知识点:使用静态工厂方法替代构造方法。以下是该知识点的详细解释: 1. 静态工厂方法与构造方法的区别:在Java中,构造方法用于创建类的实例,它与类同名并可拥有...
目录(Contents)第 2 章 创建和销毁对象(创建和气氛对象)第二章简介(章节介绍)第 1 条考虑静态工厂方法而不是构造函数(考虑以静态工厂方法代替构造函数)Item 2: 当面对许多构造函数参数时考虑构建器(在面对...
7. **优先考虑静态工厂方法(Consider Static Factory Methods Instead of Constructors)**:静态工厂方法比构造器更灵活,不强制创建新的类实例,可以返回类的子类实例,或者在没有合适构造器时提供默认实现。...
目录:一、创建和销毁对象 (1 ~ 7)二、对于所有对象都通用的方法 (8 ~ 12)三、类和接口 (13 ~ 22)四、泛型 (23 ~ 29)五、枚举和注解 (30 ~ 37)六、方法 (38 ~ 44)七、通用程序设计 (45 ~ 56)八、异常 ...
- Item1:静态工厂方法相比构造子,提供了更大的灵活性,例如可以返回对象的子类实例,或者在不改变API的情况下更改返回的对象类型。 - Item2:Builder模式适用于有大量构建参数的情况,它可以避免构造器的链式...
4. **构造函数与析构函数**:C++中的构造函数和析构函数是对象生命周期的关键部分。书中会讲解如何利用它们进行初始化和清理资源,以及何时使用构造函数初始化列表。 5. **运算符重载**:C++允许用户为自定义类型...
静态工厂方法可以返回原类型的任何子类型,且可以将构建好的实例缓存起来,方便重复利用,不用每次调用都创建新对象,从而提高性能。 静态工厂方法的惯用命名包括:from、of、valueOf、instance 或 getInstance、...
本学习记录主要介绍了 Effective Java 3 中的静态工厂方法和 Builder 模式两部分内容。 一、静态工厂方法 静态工厂方法是指返回类实例的命名规则,例如:from、of、valueOf、instance 或 getinstance、create 或 ...
1. **使用const**:在函数参数、成员函数和对象中广泛使用`const`关键字,可以提高代码的清晰性和可维护性,同时避免不必要的数据修改。 2. **避免隐式类型转换**:C++允许类之间的隐式类型转换,但这可能导致意外...
6. 初始化列表(Initializer Lists):初始化列表允许在创建对象时提供一组初始值,如`std::vector<int>{1, 2, 3}`。它们也用于构造和初始化容器类,如`std::map`和`std::set`。 7. `nullptr`关键字:`nullptr`是...
《Effective Modern C++》是Scott Meyers所著的一本经典C++编程指南,中文版的出现使得更多中国读者能够深入理解和应用C++11及后续标准的新特性。这本书旨在帮助开发者充分利用C++的新功能,提高代码质量、效率和可...
14. **`std::make_unique`和`std::make_shared`**:理解这两个工厂函数优于直接调用构造函数创建智能指针的原因。 15. **成员初始化列表**:在构造函数中使用成员初始化列表以优化对象初始化,防止不必要的拷贝和...