ITPub博客

首页 > Linux操作系统 > Linux操作系统 > 《Effective java 第二版 中文版》第2章 创建和销毁对象

《Effective java 第二版 中文版》第2章 创建和销毁对象

原创 Linux操作系统 作者:hzbook2008 时间:2009-02-17 17:08:57 0 删除 编辑
Normal 0 7.8 磅 0 2 false false false MicrosoftInternetExplorer4 第2章                      创建和销毁对象

本章的主题是创建和销毁对象:何时以及如何创建对象,何时以及如何避免创建对象,如何确保它们能够被适时地销毁,以及如何管理销毁之前必须进行的所有清除动作。

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

对于类而言,为了让客户端获取它自身的一个实例,最常用的方法就是提供一个公有的构造器。还有一种方法,也应该成为每个程序员的工具箱中的一部分。类可以提供一个公有的静态工厂方法static factory method),它只是一个返回类的实例的静态方法。下面是一个来自Boolean(基本类型boolean的包装类)的简单示例。这个方法将boolean基本类型值转换成了一个Boolean对象引用:

 

public static Boolean valueOf(boolean b) {

return b ? Boolean.TRUE : Boolean.FALSE;

}

注意,静态工厂方法与设计模式(Design Patterns[Gamma95, p.107]中的工厂方法Factory Method)模式不同。本条目中所指的静态工厂方法并不直接对应于设计模式中的工厂方法。

类可以通过静态工厂方法来提供它的客户端,而不是通过构造器。提供静态工厂方法而不是公有的构造器具有两大优势。

静态工厂方法与构造器不同的第一大优势在于,它们有名称。如果构造器的参数本身没有确切地描述正被返回的对象,那么具有适当名称的静态工厂会更容易使用,产生的客户端代码也更易于阅读。例如,构造器BigIntegerint, int, Random)返回的BigInteger可能为素数(prime),如果用名为BigInteger.probablePrime[YuLimin1] 的静态工厂方法来表示,显然要好一些。(1.4的发行版本中最终增加了这个方法。)

一个类只能有一个带有指定签名的构造器。编程人员通常知道如何避开这一限制:通过提供两个构造器,它们的参数列表只在参数类型的顺序上有所不同。实际上这并不是个好主意。这种API的用户永远也不会记得哪个构造器是哪个,最终会导致调用错误的构造器。人们读到使用了这些构造器的代码时,如果没有参考类的文档,往往不知道这段代码是做什么用的。

由于静态工厂方法有名称,所以它们没有像前一段落中所谈到那种限制。如果一个类可能需要多个带有相同签名的构造器时,就用静态工厂方法代替构造器,并且慎重地选择名称以便突出它们之间的区别。

静态工厂方法与构造器不同的第二大优势在于,不必在每次调用它们的时候都创建一个新对象。这使得不可变的类(见第15条)可以使用预先构建好的实例,或者将构建好的实例缓存起来,进行重复分发,以避免重复创建不必要的对象。Boolean.valueOf(boolean)方法示范了这种方法:它从来不创建对象。这种方法类似于Flyweight模式[Gamma95p.195]。如果程序经常请求相同的对象,尤其当创建对象的成本很高时,这种方法可以极大地提升性能。

静态工厂方法能够为重复的调用返回相同对象,这样有助于类总能严格控制在某个时刻哪些实例应该存在。这种类被称作实例受控的类(instance-controlled)。编写实例受控的类有几个原因。实例受控使得类可以确保它是一个Singleton(见第3条)或者是不可实例化的(见第4条)。它还使得不可变的类(见第15条)可以确保不会存在两个相等的实例,即当且仅当a==b的时候才有a.equals(b)。如果类保证了这一点,它的客户端就可以使用==操作符来代替equalsObject)方法,这样可以提升性能。枚举(enum)类型(第30条)保证了这一点。

静态工厂方法与构造器不同的第三大优势在于,它们可以返回原返回类型的任何子类型的对象。这样我们在选择返回对象的类时就有了很大的灵活性。

这种灵活性的一种应用是,API可以返回对象,同时又不会使它们的类变成公有的。以这种方式隐藏实现类会使API变得非常简洁。这种方法适合于基于接口的框架interface-based framework,见第18条),因为在这种框架中,接口为静态工厂方法提供了自然返回类型。接口不能有静态方法,因此按照惯例,接口Type的静态工厂方法被放在一个名为Types的不可实例化的类(见第4条)中。

例如,Java Collections Framework的集合接口有32个便利实现,它们提供了不可修改的集合、同步集合以及类似的东西。几乎所有这些实现都通过静态工厂方法在一个不可实例化的类(java.util.Collections)中导出。所有返回对象的类都是非公有的(nopublic)。

现在的Collections Framework API比导出32个独立公有类的那种实现方式要小得多,每种便利实现对应一个类。这不仅仅是指API数量上的减少,也是概念意义上的减少。用户知道,被返回的对象是由相关的接口精确指定的,所以他们不需要阅读该实现类的额外类文档。使用这种静态工厂方法时,甚至要求客户端通过接口来引用被返回的对象,而不是通过它的实现类来引用被返回的对象,一般来说这是一种良好的习惯(见第52条)。

公有的静态工厂方法所返回的对象的类不仅可以是非公有的,而且该类还可以随着每次调用而发生变化,具体取决于静态工厂方法的参数值。只要是已声明的返回类型的子类型,都是允许的。为了提升软件的可维护性和性能,返回对象的类也可能随着不同的发行版本而不同。

发行版本1.5中引入的类java.util.EnumSet(见第32条)没有公有构造器,只有静态工厂方法。它们返回两种实现类之一,具体则取决于底层枚举类型的大小:如果它的元素有64个或者更少,就像大多数枚举类型一样,静态工厂方法就会返回一个RegalarEumSet实例,用单个long进行支持;如果枚举类型有65个或者更多元素,工厂就返回JumboEnumSet实例,用long数组进行支持。

这两个实现类的存在对于客户端来说是不可见的。如果RegularEnumSet不能再给小的枚举类型提供性能优势,就可以从未来的发行版本中将它删除,不会造成不良的影响。同样地,如果事实证明对性能有好处,也可能在未来的发行版本中添加第三甚至第四个EnumSet实现。客户端永远不知道也不关心他们从工厂方法中得到的对象的类;他们只关心它是EnumSet的某个子类即可。

静态工厂方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不必存在。这种灵活的静态工厂方法构成了服务提供者框架Service Provider Framework)的基础,例如JDBCJava数据库连接, Java Database ConnectivityAPI。服务提供者框架是指这样一个系统:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把他们从多个实现中解耦出来。

服务提供者框架中有三个重要的组件:服务接口(Service Interface),这是提供者实现的;提供者注册APIProvider Registration API),这是系统用来注册实现,让客户端访问它们的;服务访问APIService Access API),是客户端用来获取服务的实例的。服务访问API一般允许但是不要求客户端指定某种选择提供者的条件。如果没有这样的规定,API就会返回默认实现的一个实例。服务访问API是“灵活的静态工厂”,它构成了服务提供者框架的基础。

服务提供者框架的第四个组件是可选的:服务提供者接口(Service Provider Interface),提供者实现它来创建其服务实现的实例。如果没有服务提供者接口,实现就按照类名称注册,并通过反射方式进行实例化(见第53条)。对于JDBC来说,Connection就是它的服务接口,DriverManager.registerDriver是提供者注册APIDriverManager.getConnection是服务访问APIDriver就是服务提供者接口。

服务提供者框架模式有着无数种变体。例如,服务访问API可以利用Adapter模式[Gamma95p.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 providers =

new ConcurrentHashMap();

public static final String DEFAULT_PROVIDER_NAME = "";

// 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> m =

new HashMap>();

 

随着类型参数变得越来越长,越来越复杂,这一冗长的说明也很快变得痛苦起来。但是有了静态工厂方法,编译器就可以替你找到类型参数。这被称作type inference。例如,假设HashMap提供了这个静态工厂:

 

public static HashMap newInstance() {

return new HashMap();

}

你就可以用下面这句简洁的代码代替上面这段繁琐的声明:

Map> m = HashMap.newInstance();

 

总有一天,Java将能够在构建器调用以及方法调用中执行这种type inference,但到发行版本1.6为止暂时还无法这么做。

遗憾的是,到发行版本1.6为止,标准的集合实现如HashMap并没有工厂方法,但是可以把这些方法放在你自己的工具类中。更重要的是,可以把这样的静态工厂放在你自己的参数化的类中。

静态工厂方法的主要缺点在于,类如果不含公有的或者受保护的构造器,就不能被子类化。对于公有的静态工厂所返回的非公有类,也同样如此。例如,要想将Collections Framework中的任何方便的实现类子类化,这是不可能的。但是这样也许会因祸得福,因为它鼓励程序员使用复合(composition),而不是继承(见第16条)。

静态工厂方法的第二个缺点在于,它们与其他的静态方法实际上没有任何区别。在API文档中,它们没有像构造器那样在API文档中明确标识出来,因此,对于提供了静态工厂方法而不是构造器的类来说,要想查明如何实例化一个类,这是非常困难的。Javadoc工具总有一天会注意到静态工厂方法。同时,你通过在类或者接口注释中关注静态工厂,并遵守标准的命名习惯,也可以缩小这一劣势。下面是静态工厂方法的一些惯用名称:

l        valueOf——不太严格地讲,该方法返回的实例与它的参数具有相同的值。这样的静态工厂方法实际上是类型转换方法。

l        of——valueOf的一种更为简洁的替代,在EnumSet(见第32条)中使用并流行起来。

l        getInstance——返回的实例是通过方法的参数来描述的,但是不能够说与参数具有同样的值。对于Singleton来说,该方法没有参数,并返回唯一的实例。

l        newInstance——像getInstance一样,但newInstance能够确保返回的每个实例都与所有其他实例不同。

l        getType——像getInstance一样,但是在工厂方法处于不同的类中的时候使用。Type表示工厂方法所返回的对象类型。

l        newType——像newInstance一样,但是在工厂方法处于不同的类中的时候使用。Type表示工厂方法所返回的对象类型。

简而言之,静态工厂方法和公有构造器都各有用处,我们需要理解它们各自的长处。静态工厂通常更加合适,因此切忌第一反应就是提供公有的构造器,而不先考虑静态工厂。

2条:遇到多个构造器参数时要考虑用构建器

静态工厂和构造器有个共同的局限性:它们都不能很好地扩展到大量的可选参数。考虑用一个类表示包装食品外面显示的营养成份标签。这些标签中有几个域是必需的:每份的含量、每罐的含量以及每份的卡路里,还有超过20个可选域:总脂肪量、饱和脂肪量、转化脂肪、胆固醇、钠等等。大多数产品都只有几个可选域中会有非零的值。

对于这样的类,应该用哪种构造器或者静态方法来编写呢?程序员一向习惯采用telescoping constructor模式,在这种模式下,你提供一个只有必要参数的构造器,第二个构造器有一个可选参数,第三个有两个可选参数,依此类推,最后一个构造器包含所有可选参数。下面有个示例,为了简单起见,它只显示四个可选域:

// Telescoping constructor pattern - does not scale well!

public class NutritionFacts {

private final int servingSize; // (mL) required

private final int servings; // (per container) required

private final int calories; // optional

private final int fat; // (g) optional

private final int sodium; // (mg) optional

private final int carbohydrate; // (g) optional

public NutritionFacts(int servingSize, int servings) {

this(servingSize, servings, 0);

}

public NutritionFacts(int servingSize, int servings,

int calories) {

this(servingSize, servings, calories, 0);

}

public NutritionFacts(int servingSize, int servings,

int calories, int fat) {

this(servingSize, servings, calories, fat, 0);

}

public NutritionFacts(int servingSize, int servings,

int calories, int fat, int sodium) {

this(servingSize, servings, calories, fat, sodium, 0);

}

public NutritionFacts(int servingSize, int servings,

int calories, int fat, int sodium, int carbohydrate) {

this.servingSize = servingSi

Effective Java中文版第二版.jpg

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/16502878/viewspace-553589/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2008-10-23

  • 博文量
    209
  • 访问量
    753146