第一条:静态工厂方法代替构造器
静态工厂方法是返回一个类的实例的静态方法(此处介绍的静态工厂方法并不对应设计模式中的工厂模式),例:基本类型boolean转化为包装类:
public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
提供静态工厂方法而不是构造方法的优势:
- 静态工厂方法有名称,代码易于阅读。构造方法可以重载,但是函数名同类名,无法区分具体实现功能。所以一个类需要多个相同签名的构造器时,应提供静态工厂方法,同时函数名上加以区分。
- 静态工厂方法不必在每次调用时都创建一个新的对象。
- 可以返回原类型的任何子类型对象。应用:接口返回对象,同时不使对象的类变成公有的。
- 返回对象的类可以随着参数不同而改变,只要是已声明返回类型的子类型都是允许的。例:EnumSet没有公有的构造器,只有静态工厂方法,在noneOf静态工厂方法中,判断底层枚举类型的大小,返回不同类型的实例,同时不会影响客户端。
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<>(elementType, universe); else return new JumboEnumSet<>(elementType, universe); }
- 方法返回对象的所属的类,在编写该静态方法时可以不存在。
静态工厂方法的缺点:
- 类如果不含有公有的或者受保护的构造器,将不可以被继承。
- 静态工厂方法难以被发现。常用函数名:from、of、valueOf、instance、create、gatType、newType、type。
第二条:遇到多个构造器参数时考虑使用构建器
构建器解决了构造器和静态工厂方法的局限性:当一个类有大量的可选参数时,需要重写多个构造器,代码难以维护。同时也有人提出使用JavaBean的方式(即setParam()),但是这种方式有一个很大的缺陷就是,构造过程被分到了许多调用中,JavaBean可能会处于数据不一致的状态。并且JavaBean模式把类做成不可变的可能性不复存在,需要额外保证它的线程安全。
构造器模式实现:
class NutritionFacts { private final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate; public static class Builder { private final int servingSize; private final int servings; private int calories = 0; private int fat = 0; private int sodium = 0; private int carbohydrate = 0; public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; } public Builder calories(int val) { calories = val; return this; } public Builder fat(int val) { fat = val; return this; } public Builder sodium(int val) { sodium = val; return this; } public Builder carbohydrate(int val) { carbohydrate = val; return this; } public NutritionFacts build() { return new NutritionFacts(this); } } private NutritionFacts(Builder builder) { this.servings = builder.servings; this.calories = builder.calories; this.carbohydrate = builder.carbohydrate; this.fat = builder.fat; this.sodium = builder.sodium; this.servingSize = builder.servingSize; } NutritionFacts nutritionFacts = new NutritionFacts.Builder(10,10) .calories(5) .carbohydrate(5) .fat(100) .sodium(7) .build(); }
第三条:用私有构造器或者枚举类型强化Singleton属性
Singleton是指仅被实例化一次的类,通常用来代表一个无状态的对象,或本质上唯一的系统组件,实现Singleton的常用方式是将构造器变为私有。如下两种方式:
class Singleton{ public static final Singleton INSTANCE = new Singleton(); private Singleton(){ } } class Singleton2{ private static final Singleton2 INSTANCE2 = new Singleton2(); private Singleton2(){ } public static Singleton2 getInstance(){ return INSTANCE2; } }
在方法一中,如果客户端使用setAccessible(true)方法调用私有的构造方法,实例的唯一性将无法保证,此时可以在构造器中判断,如已存在当前类的实例则抛出异常。如下:
public class SingletonDemo { public static void main(String[] args) throws Exception { Singleton singleton1 = Singleton.INSTANCE; Singleton singleton2 = Singleton.INSTANCE; Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton singleton3 = constructor.newInstance(); System.out.println(singleton1 + "~" + singleton2 + "~" + singleton3); } } class Singleton{ public static final Singleton INSTANCE = new Singleton(); private Singleton(){ if(Objects.nonNull(INSTANCE)){ throw new RuntimeException("实例已存在!!"); } } }
如果要将Singleton类支持序列化,仅仅实现Serializable接口是不够的,为了保证每次反序列化时不构造一个新的Singleton对象,需要提供readResolve方法。
第三种也是最佳的实现Singleton类的方法是声明一个包含单个元素的枚举类型,这种方式更加简洁,并且提供了序列化机制,避免重复生成实例。