Effective Java中文第二章第1节(个人渣翻)

来源:互联网 发布:b站视频 知乎 编辑:程序博客网 时间:2024/05/14 08:21
本章将着重讨论有关对象的创建与销毁的课题:什么时候该用什么方法来创建对象,什么时候又该用什么方法来避免创建对象,怎样在适当的时候销毁对象,在对象销毁前又应当进行怎样的操作呢?

第1节:用静态工厂方法来替代构造方法

用户一般通过类的构造方法来获取它的实例对象。但每个程序员也应当掌握另外一种获取实例对象的技术,也就是通过类的静态工厂方法来创建对象。以下是Boolean(boolean的包装类)的静态工厂方法,该方法将基本数据类型boolean转化成了一个Boolean对象:

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

请注意,静态工厂方法有别于工厂模式 [Gamma95, p. 107]。本节所说的静态工厂方法跟设计模式无关。
除了构造方法之外,一个类也可以提供静态工厂方法,它的一些优缺点如下。

静态工厂方法的优点之一:不同于构造方法,我们可以给它命名。当构造方法的参数不足以描述返回的对象时,我们可以命名一个适当的静态工厂方法,这样更便于调用方法和提升代码的可读性。举个例子,BigInteger的构造方法:BigInteger(int, int, Random),它却可能返回一个基本数据类型,在这种情况下,我们可以创建一个可读性更强的静态工厂方法 BigInteger.probablePrime。(该方法已在JDK1.4realse中添加)。
对于给定的特征规则,一个类只有一种构造方法。有些程序员会通过构建参数顺序不同的构造方法来作为区分,这简直就是傻逼。因为对于调用者来说,他很有可能会调用错误的构造方法;而对于开发人员来说,没有文档的话,他也无法辨别这几个构造方法到底有什么区别。
而静态工厂方法由于拥有了自己的命名,就完全不会引起上述问题。所以,当一个类在给定的特征规则下需要多种构造方法时,请构建不同的静态工厂方法,并给予可读性强的命名加以区分。

静态工厂方法的优点之二:不同于构造方法,静态工厂方法在被调用时不会每次都创建新的对象。它允许不可变类(第15节)复用已创建的实例对象,或者在创建的时候缓存实例对象,这样可以避免创建不必要的重复对象。Boolean的valueOf(boolean)方法很好地阐述了这一优点:它永远不会创建新的对象,这一特性跟轻量级设计模式很相似[Gamma95, p. 107]。如果你需要创建大量重复的对象,并且创建它的代价很大的话,那就试试这种方法吧,它对性能的提升有极大的帮助。
静态工厂方法保证了类在任何时候都可以提供它的引用对象并对其进行严格地管理。这种机制称为类的对象控制,那么为什么要构建这样的类呢?1.对象控制保证了类创建的对象都是单例的(第3节);2.它可以保证不可变类不会创建两个完全相同的对象(a.equals(b) if and only if a==b)。在这样的前提下,调用者可以放心的使用 == 而不是 equals(Object)方法来比较两个对象,有啥用?可以提升性能呀!Enum类便具有这种特性。

静态工厂方法的优点之三:不同于构造方法,静态工厂方法可以返回对象的子类对象。你可以更加灵活地选择所需要创建的对象类型。
这种灵活性的用途之一便是可以创建私有化的类对象。将类创建的实现细节隐藏会使我们的API显得更加简洁。这一方法较多的应用于接口型框架中,接口仅需为静态工厂方法定义需要的返回类型即可。由于接口类不能创建静态方法,依照惯例,在面向接口(Type)开发时,可以在另一个非实例类(第4节)(Types)中创建静态工厂方法。
例如,Java的Collections框架拥有32种实现类,包括不可修改的colletions,同步型collecitons等等。几乎所有的这些实现类都可以通过非实例类(java.util.Collections)的静态工厂方法调用,但这些类的返回对象却不全是public的。
如果Collections API为这32种各自提供一种实现的话,那它的体积将会增加数倍。除此之外,静态工厂方法还削减了API的概念权重。调用者无需阅读冗长的文档,仅通过接口定义的API,便能准确地了解方法返回的类型特征。再多说一点,相比于通过实现类来引用对象,通过静态工厂方法来引用的话通常会更好一些(第52节)。
静态工厂方法返回的对象不但可以不是public的,也会因为静态工厂的调用参数的不同而不同,甚至在软件的升级时也会产生差异,而且它也允许返回返回类型的子类对象。
java.util.EnumSet类(第32节)在JDK1.5中还没有公有的构造方法,只有静态工厂。它会依据enum的大小返回两种不同的实现类,具体如下:当enum包含的字段少于64个时(大多数都是),静态工厂返回的是RegularEnumSet实例,实际是long类型的;当enum包含的字段大于64个时,静态工厂返回的是JumboEnumSet实例,实际是一个long的数组。
对于调用者来说,这两个类的实现是不可见的。当RegularEnumSet对于小的enum类型不再有性能优势时,它将会在未来版本中被安全的移除。同样的,一旦性能上无法满足需求,未来版本也会加入第三甚至第四种EnumSet的实现类。调用者既不用知道也不用关心工厂返回的是哪个类,只要知道它是EnumSet的子类就行了。
静态工厂方法返回的类无需存在于包含该方法的类中。灵活多变的静态工厂方法组成了服务型框架(service provider frameworks),如Java Database Connectivity API (JDBC)。
服务型框架是指由服务方来实现具体服务,系统再将服务提供给调用者,以避开自身去实现的方式来解耦的框架。服务型框架有三大重要组成部分:服务接口(service interface),由服务方实现;提供者注册API(provider registration),由系统将已实现的服务注册,并提供给调用者;服务访问API(service access),用于调用者获取服务实例,它允许但不需要调用者去定义服务提供方的每个细节。在缺少定义细节的情况下,API会构造默认参数的实例并返回给调用者。也可以说,服务访问API即是服务型框架中的“灵活型静态工厂”。
服务型框架的模式非常之多。例如,服务访问API可以通过适配器模式(Adapter pattern),提供一个远超调用者需求的服务接口[Gamma95, p. 139]。以下是一个服务提供接口的简单实现和一个默认的提供者。

// Service provider framework sketch   // 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 (Item 4)       // 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();       }}

静态工厂方法的优点之四:静态工厂方法减少了冗长的参数化实例的创建。操蛋的是,当你调用一个参数化类的构造方法时,即使是上下文已经非常明显,你还必须声明各个参数的类型。就像下面那样,你得声明两次:

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会在构造方法或其他方法中增加这种调用方式,但毕竟1.6还没有(1.6之后的版本泛型已不用二次声明)。
更操蛋的是,标准的集合框架实现类,如HashMap,在1.6版本中也没有工厂方法!怎么办,你可以把这些方法放到你自己的工具类中。更重要的是,你可以把这些静态工厂放到你自己的参数化类中:)
下面我们来讲讲它的缺点~

静态工厂方法的缺点之一:如果类没有public或者proteced级别的构造方法,静态工厂方法就无法提供它的子类。对于公有静态工厂返回的非public类来说也是一样的。例如,你无法创建一个Collections框架中的实现类的子类。因祸得福的是,这样的机制鼓励大家多使用组合而非继承(第16节)。

静态工厂方法的缺点之二:静态工厂方法很难跟其他静态方法区分。不像构造方法,它们并不存在于API文档中。所以你很难想象,到底该怎么去创建一个带有静态工厂方法而不是构造方法的类呢。也许某一天,Javadoc会看到静态工厂方法身上的光彩。为了减少这个缺点带来的影响,你可以遵守一些静态工厂方法通用的命名规范,如下:
● valueOf—返回一个跟参数值相同的对象。
● of—低配valueOf, 由EnumSet兴起 (Item 32).
● getInstance—返回一个有各种参数的实例,但不能说它就是参数本身。在单例模式中,getInstance方法没有参数并且只返回单一实例。
● newInstance—跟getInstance类似,但newInstance创建的对象都是独立的。
● getType—跟getInstance类似,工厂方法在另一个类中时使用。Type声明了工厂方法返回的对象类型。
● newType—跟newInstance类似,工厂方法在另一个类中时使用。Type声明了工厂方法返回的对象类型。

阅读全文
0 0
原创粉丝点击