让对象的创建与销毁在掌控中。
Item 1: 使用静态工厂方法而非使用构造函数
public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
优势:
1. 方法名+参数名,相较于构造函数,能更好的描述返回对象;
BigInteger(int, int, Random) BigInteger.probablePrime(int, int, Random)
2. 不会像构造函数那样,每次调用不一定必须返回新对象;
利用静态工厂方法可以得到类的单例对象,也可以辅助得到无法直接使用构造函数实例化的类的实例。
public class Singleton { private static Singleton instance = new Singleton(); private Singleton(){ //在类外部无法用私有构造函数实例化 } public static Singleton getInstance(){ return instance; } public static void main(String[] args) { //在类内部可以通过私有构造函数实例化 Singleton instance = new Singleton(); Singleton instance1 = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); //比较实例地址 System.out.println(instance1 == instance2); } }
3. 可以返回子类对象
相比较于构造函数,静态工厂方法可以返回子类对象。这样的话,就可以将各个子类的具体实现隐藏起来,再通过静态工厂方法将功能暴露出来,使得方法易用而且也不用关心具体实现。理解的有些抽象,可以拿java.util.Collections举例。Collections无法实例化且方法全部为静态式,包含大量内部类且Default访问权。
隐藏子类的具体实现,并通过接口和静态工厂方法暴露功能,这样设计的一大好处就是将API集中起来易于使用和维护。
4. 同一方法调用可以返回不同子类的对象
乍看起来和第3条类似,但第3条指静态方法返回某个子类的对象,第4条指静态方法返回不同子类的对象。比如EnumSet类,有子类RegularEnumSet和JumboEnumSet。根据内部枚举类型,可以返回RegularEnumSet或者JumboEnumSet实例(也是面向接口编程的优势)。
5. 静态工厂方法所在类同静态工厂方法返回对象的类之间可以相互独立
感觉啰嗦,实际就是静态工厂方法返回对象的类可以后续再实现。这也是面向接口编程带来的好处 - 服务提供框架,具体例子可参考《谈谈服务提供框架》
缺点:
1. 一般只有静态方法的类,其构造函数会声明为private。这样该类就无法被继承。但可以通过组合模式解决。
2. 在目前的API文档中,没有对静态工厂方法做特殊处理,导致在查阅方法时很容易忽略(个人认为这个是小问题,时常想着先考虑静态工厂方法即可)
Item 2: 当构造参数过多时,应当考虑构建者模式
当构造参数过多时,如果使用构造函数创建对象,需要写很多而且代码大量重复,不易使用;如果采用JavaBean的方式,则会把创建对象的过程分拆到一个个的set方法中,存在线程安全问题(对JavaBean的一点改进)。这个时候要考虑到构建者(Builder)模式。同时,也可以通过Builder模式创建出不可变对象。
public 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; static class Builder { //Required parameters private final int servingSize; private final int servings; //Optional parameters - initialized to default values private int calories = 0; private int fat = 0; private int sodium = 0; private int carbohydrate = 0; protected Builder(int servingSize, int servings){ this.servingSize = servingSize; this.servings = servings; } public Builder calories(int calories){ this.calories = calories; return this; } public Builder fat(int fat){ this.fat = fat; return this; } public Builder sodium(int sodium){ this.sodium = sodium; return this; } public Builder carbohydrate(int carbohydrate){ this.carbohydrate = carbohydrate; return this; } public NutritionFacts build(){ return new NutritionFacts(this); } } private NutritionFacts(Builder builder){ this.servingSize = builder.servingSize; this.servings = builder.servings; this.calories = builder.calories; this.fat = builder.fat; this.sodium = builder.sodium; this.carbohydrate = builder.carbohydrate; } public int getServingSize() { return servingSize; } public int getServings() { return servings; } public int getCalories() { return calories; } public int getFat() { return fat; } public int getSodium() { return sodium; } public int getCarbohydrate() { return carbohydrate; } }
创建对象代码示例:
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build()
可以看到,想比于构造函数或者JavaBean的方式,Builder模式利用静态内部Builder类 + 链式调用创建实例。
当构造函数参数 >=4 个时,优先考虑Builder模式创建对象。
Item 3: 通过私有化构造函数或者枚举类来创建单例
创建单例的方式常用的有如下两种(这里不考虑double-lock checking避免多线程的问题),都需要将构造函数私有化。
1. 静态成员变量 + 私有构造函数
public class Elvis1 { public static final Elvis1 Elvis1 = new Elvis1(); private Elvis1(){ System.out.println("private constructor to make instance singleton"); } public void check(){ } }
2. 静态工厂方法 + 私有构造方法
public class Elvis2 { private static final Elvis2 ELVIS_2 = new Elvis2(); private Elvis2(){ System.out.println("private constructor to make instance singleton"); } public void check(){ } public static Elvis2 getInstance(){ return new Elvis2(); } }
相比之下,静态工厂方法更加灵活些,可以控制方法返回的对象。
需要注意的一点,如果一个单例类需要序列化,那只implements Serializable是不够的,还需要以下2点:
(1) 字段声明 transient
(2) 提供readResolve()方法,否则在反序列化后会创建新的实例,而不是单例了。
public class Elvis implements Serializable{ private static final Elvis ELVIS = new Elvis(); private Elvis(){ System.out.println("private constructor to make instance singleton"); } public static Elvis getInstance(){ return ELVIS; } private Object readResolve(){ // Return the one true Elvis and let the garbage collector take care of the Elvis impersonator. return ELVIS; } }
测试代码如下:
public static void main(String[] args) { try { //serializable and deserializable Elvis elvis = Elvis.getInstance(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("t.tmp")); oos.writeObject(elvis); oos.close(); System.out.println("object serialized"); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("t.tmp")); Object deElvis = ois.readObject(); ois.close(); System.out.println("object de-serialized"); System.out.println(elvis); System.out.println(deElvis); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
如果没有实现readResolve(),输出如下。对象地址不同,也就是反序列化创建了新对象。实现readResolve()方法保证序列化和反序列化得到对象唯一。
private constructor to make instance singleton object serialized object de-serialized com.chris.item3.code.Elvis@6d6f6e28 com.chris.item3.code.Elvis@135fbaa4
3. 单元素枚举
这种方式保证实例唯一,反序列化后仍然唯一,而且最简洁。
public enum Elvis { INSTANCE; public void sayHello(){ System.out.println("hello, a single-element enum!"); } }
public static void main(String[] args) { Elvis elvis = Elvis.INSTANCE; elvis.sayHello(); }
Item 4: 构造函数私有化,使得类无法实例化
当类不需要有实例时(比如工具类),最好将构造函数私有化。
public class Utility { private Utility() { throw new AssertionError("tool class, can't be instanced"); } public static String toUpperCase(String s){ return s.toUpperCase(); } }
Item 5: 依赖注入而不是将依赖写死在类中
Item 9: 用 try-with-resource代替try-finally
类实现AutoCloseable后,为保证最终调用close()以释放资源,会使用try-finally的方式。
public static String firstLineOfFile2(String path) throws IOException { BufferedReader br = new BufferedReader(new FileReader(path)); try { return br.readLine(); } finally { br.close(); } }
但这样存在2个问题,
1. 当方法中涉及到多个资源对象时,需要finally中分别调用close()
2. 当try块和finally块中同时抛出异常时,try块中的异常将被掩盖,往往try块中的异常是我们想要的。
使用try-with-resource方式可以避免以上问题,
public static String firstLineOfFile(String path) throws IOException { try (BufferedReader br = new BufferedReader(new FileReader(path))) { return br.readLine(); } }
资源对象的close()方法将隐式进行,并且当try块中抛出异常时会抑制(suppress) close()方法抛出的异常,这样我们就得到了感兴趣的异常。
在实际业务场景中,更多的是try-catch-finally方式,即捕获感兴趣的异常并处理。
public static void copy2(String src, String des){ //try-catch-finally InputStream in = null; OutputStream os = null; try { in = new FileInputStream(src); os = new FileOutputStream(des); byte[] buffer = new byte[BUFFER_SIZE]; int num = 0; while ((num = in.read(buffer)) >= 0){ os.write(buffer, 0, num); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (in != null){ in.close(); } if (os != null){ os.close(); } } catch (IOException e) { e.printStackTrace(); } } }
用try-with-resource方式可以简化以上代码(省去finally块),更加简洁。
public static void copy(String src, String des){ //try-with-resource try (InputStream in = new FileInputStream(src); OutputStream os = new FileOutputStream(des)) { byte[] buffer = new byte[BUFFER_SIZE]; int num = 0; while ((num = in.read(buffer)) >= 0){ os.write(buffer, 0, num); } } catch (IOException e) { e.printStackTrace(); } }
总之,当类实现AutoCloseable后,为保证close()的调用,使用try-with-resource方式。