第 1 条:考虑用静态工厂方法代替构造器
对于类而言,为了让客户端获取它自身的一个实例,常用的方法是提供一个公共的构造器。 还有一种方法:类可以提供一个共有的静态工厂方法,它只是一个返回类的实例的静态方法。。下面是一个返回 Boolean 类的实例的例子:
public static Boolean valueOf(boolean b){ return b ? Boolean.TRUE : Boolean.FALSE;}复制代码
静态工厂方法,不等同于设计模式中的工厂方法 这样做的几大优势:
- 静态工厂方法与构造方法不同的第一个优势在于,方法具有名称。构造方法本身不能带有任何具有字面意义的方法签名,无法从方法名字中推测出方法调用后,产生的实例的作用或者配置;而静态工厂方法可以增加方法签名,例如 BigInteger(int,int,Random) 返回的 BigInteger 可能为素数,如果用名为 BigInteger.proablePrime 的静态工厂方法,显然更为清楚。
- 不必在每次调用它们的时候,都创建一个新的对象。这使得不可变类可以使用预先构建好的实例,或者将构建好的实例缓存起来,重复利用,避免重复创建对象。 Boolean.valueOF(boolean) 这个方法就说明了这一点:它从来不需要创建对象,直接返回一个静态成员变量。
- 第三个优势:它们可以返回 原类型 的任何子类型的对象。这样的灵活性在于:API 可以返回申明类型的子类型,同时又能确保返回的子类不会变成公开的。Java Collections Framework 的集合接口共有32个便利实现,分别提供了不可修改的集合、同步集合等。几乎所有这些实现都通过静态工厂方法在一个不可实例化的类中导出,返回对象的类都是非公有的。
- 公有的静态工厂方法,所返回的对象的类不仅可以是私有的,而且该类还可以随着每次调用发生变化。例如:
public static Number getInstance(int b){ if(b > 0){ return new NumberPositive(); else{ return new NumberNegative(); }}复制代码
java.util.EmunSet 没有公共构造方法,只有静态工厂方法,它们返回两种实现之一,取决于底层枚举类型的大小。小于等于 64 个时,会返回 RegalarEnumSet 实例,用 long 进行支持;大于 64 个则返回 JumboEnumSet 实例,使用 long[] 进行支持。
静态工厂返回的类,在编写时该类可以不需要存在,这种灵活的方法构成了服务提供者框架基础。例如 JDBC API。它们提供一个服务,系统为客户端提供多个实现,并把它们从多个实现中解耦出来。
服务提供者框架中有三个重要组件:
- 服务接口 Service Interface :提供者实现
- 提供者注册 API :系统用来注册实现,让客户端访问它
- 服务访问 API :客户端用来获取服务的实例的。
这一段其实我也有点没看懂。
- 第四个优点:创建泛型实例的时候,它们使代码变得更加简洁。例子:
Map> m = new HashMap >();复制代码
如果出现了很长的泛型,那么泛型列表也将变得冗余。有了静态工厂方法,编译器可以找到参数类型,这称作类型推导。
public staticHashMap newInstance(){ return new HashMap ();}Map > m = HashMap.newInstance();复制代码
但是它的缺点在于:类如果不含有公共的或者受保护的构造方法,就不能被子类化,对于公有的静态方法所返回的非共有类,同样如此。例如,想要将 Collections Framework 中的任何方便的实现类子类化,是无法实现的。 第二个缺点在于,它们与其他的静态方法没有任何区别。在 API 文档中,并不能明显地突出它们。我们在使用是,应当遵守一些名称规范,便于使用:
- valueOf - 该方法返回的实例与它的参数具有相同的值,实际上是一个类型转换方法
- of - 简单的替代 valueOf
- getInstance - 获取一个实例,可能内部使用单例实现
- newInstance - 明确声明,获取一个新的实例
第 2 条:遇到多个构造器参数时,要考虑 Builder 模式
静态工厂方法和构造方法都不能很好的处理大量的可选参数的初始化。例如创建一个食物对象 Food,它包含重量、价格、名称意外,可能还有很多食物属性。 对于这样的类,为每个可选属性都编写一个构造方法,很显然是不明智的操作。这里不上代码了。 利用构造方法的缺点很明显:
- 不能保证传入构造器的参数一定合理(顺序传错了不会得到错误,但是可能出现业务逻辑错误)
- 修改、拓展性繁琐
另外还有一种方法,那就是通过 Java Bean 的方法。在创建对象后,调用所有需要初始化的属性的 setter 方法,注入属性值。这也很麻烦。
Person person = new Person();person.setName("Xbigfat");person.isMale(true);person.setAge(24);...复制代码
而且可能遗漏某个属性,造成业务逻辑出错。此外,这些成员变量还不可以使用 final 修饰,程序员需要付出额外的精力来确保线程安全。 好了,引出 Builder 模式吧。建造者模式不直接生产对象,而是通过 Bean 中的静态内部类链式注入需要的所有属性,最后生成对象。 在调用过程中,可以及时判断出不符合逻辑的属性,并跑出检查异常,在建造过程中,可以找到遗忘的属性,抛出运行时异常。
public class Person{//实体属性 private final String name; private final boolean isMale; private final int age; private final int weight; public static class Builder{ //必须参数 private final String name; private final boolean isMale; //可选参数 private final int age; private final int weight; //构建 Builder 对象 public Builder(String name,boolean isMale){ this.name = name; this.isMale = isMale; } //链式调用传入属性 public Builder(int age){ this.age = age; return this; } //链式调用传入属性 public Builder(int weight){ this.weight = weight return this; } //创建对象并返回 public Person build(){ return new Person(this); } } //读取建造者传递的属性,生成对象 private Person(Builser builder){ this.name = builder.name; this.isMale = builder.isMale; this.age = builder.age; this.weight = builder.weight; }}//客户端代码:Person person = new Person.Builder("Xbigfat",true).age(24).build();复制代码
简洁明朗,美美哒。 Builder 的优势就在于,每个属性的操作都是独立的,不必重新编写优化构造方法。
第 3 条:用私有构造方法或者枚举类型强化 Singleton 属性
Signleton 指仅仅被实例化一次的类,在程序运行期间,只会生成一个对象,实例化一次。下面是一种实现 Singleton 的方法:
public class Singleton{ private static final Singleton instance = new Singleton(); private Singleton(){ //将构造方法私有化 } public stacic Singleton getInstance(){ return instance; }}复制代码
私有构造器仅能够被 Singleton 的成员变量调用一次,一旦成员变量 instance 被初始化后(静态的会自动初始化),final 修饰将确保其引用不可改变。但这种方式并不能防止反射的方式,构建新的对象。 为了使这种方法实现的单例对象能够序列化,仅仅在声明中加上 implements Serializable 是不妥的,还需要将所有的实例声明为 transient 并提供一个 readResolve 方法。否则,每次反序列化时都会生成一个新的实例。 从 JDK 1.5 开始,实现 Singleton 还有一种方法:编写一个包含单个元素的枚举类型:
public enum Singleton{ instance; ...}复制代码
这种方法简介,并提供了序列化机制,绝对防止多次实例化,单元素的枚举类型已经成为实现 Singleton 的最佳方法。
第 4 条:通过私有化构造方法,强化不可实例化的属性
有时候可能需要编写 只包含 静态方法 和 静态域 的类。有些人可能在编程中滥用这样的类来编写过程化的程序。我们可以利用这种类,以 java.lang.Math / java.util.Arrays 的方式,把基本类型的值或者数组类型上的相关方法组织起来。我们也可以通过 java.util.Collections 的方式,把实现特定接口的对象上的静态方法组织起来。 这样的工具类不需要被实例化,实例化它们没有意义。然而,在没有声明构造方法的情况下,编译器会自动为它们生成一个公共的、无参的默认构造方法。 企图通过将类做成 抽象类 来强制该类不可被实例化也是行不通的,该类可以被继承,并且子类可以实例化,这样做甚至会误导用户需要自己实现子类。 最佳实践方式:
public class UtilClass{ private UtilClass(){ throw new UnsupportedOperationError(); }}复制代码
第 5 条:避免创建不必要的对象
一般来说,最好是能够重用对象,而不是每次都新建一个对象。重用的方式快速、节省资源。如果对象是不可变的,它就始终可以被重用。 反面教材:
String s = new Sting("stringette");复制代码
该语句每次被执行的时候,都会创建一个新的 String 实例,但是这些创建对象的动作全都是不必要的。传递给 String 的构造参数本身就是一个 String 实例,与构造方法创建的对象完全一样。如果这种用法出现在循环中,就会出现许多个完全不必要的实例。改进后的版本:
String s = "stringette";复制代码
这种方式只用了一个 String 的实例,而不是每次执行的时候都创建新的实例。而且,它可以保证,对于在同一台虚拟机中运行的代码,只要它们包含相同的字符串字面常量,该对象就会被重用。 对于同时提供了静态工厂方法和构造器的不可变类,通常可以使用静态工厂方法而不是构造方法,以避免重复创建对象。例如 Boolean.valueOf() 方法,不会每次都重复创建对象。 两个例子,避免创建重复对象后,效率增加:
- 计算一个人的出生日期是否在某个区间内。由于 Calendar 这个对象是固定的,所以创建一次即可,在比较的时候,可以重用该对象。频繁的创建该对象,将浪费大量资源
- 避免自动装箱带来的性能损耗,优先使用基本类型,当心不必要的自动装箱
public stativ void main(String[] args){ Long sum = 0L; for(long i = 0; i < Ingeger,MAX_VALUE ; i++){ sum += i; }}复制代码
这段程序的计算结果是正确的,但是 Long 不等于 long,自动装箱将 long 转为 Long 时,大约创建了 MAX_VALUE 个 Long 对象,带来的差异可想而知。在我自己的测试中,分别用了 700ms 和 7000ms。
当然,也不是一味的需要避免创建对象,在某些小对象的创建下,现有 JVM 不会造成太大的代价,能够提升程序阅读流畅、简洁性的创建对象,是可取的做法。
第 6 条:消除过期对象的引用
及时将不需要的变量设置为 null 可以避免内存泄漏。