考虑静态工厂方法而不是构造函数!

用静态工厂方法替代构造器

静态工厂方法具有名称,更具有可读性,尤其是构造器比较多的类。

通过静态工厂方法创建对象时,可以不必每次创建对象,使用预先构建好的实例,例如Boolean类。

  • 工厂方法可以返回原类型的任何子类,这相比构造器更加灵活。
1
2
3
public static Boolean valueOf(boolean b) {  
return (b ? TRUE : FALSE);
}

遇到多个构造器参数时考虑使用构建器

静态工厂方法和构造器都不能很好的扩展到大量参数。

对于大量参数的场景,一种方法是使用重叠构造器的方式:

第一个构造器只有少量必要参数,第二个构造器除了必要参数,还添加一些可选参数,以此类推。

  • 重叠构造器在参数量可控的时候还好,随着参数增多,构造器方法也会爆炸式增多,变得难以维护。

另一种办法是Java Beans模式,通过无参构造器构造对象,调用setter方法传递参数。

  • 但是这种方法会导致对象在构建过程中处于不一致状态,而且把类做成不可变的可能不复存在。

构建器模式通过让调用方传递必要参数调用Builder类的构造器得到builder对象。

  • 然后调用可选参数的setter方法设置可选参数,最后调用build方法生成不可变的最终对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public final class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;

// 私有构造函数,仅通过Builder类实例化
private NutritionFacts(Builder builder) {
this.servingSize = builder.servingSize;
this.servings = builder.servings;
this.calories = builder.calories;
}

// Getter方法
public int getServingSize() {
return servingSize;
}

public int getServings() {
return servings;
}

public int getCalories() {
return calories;
}

// 构建器类
public static class Builder {
// 必填属性
private final int servingSize;
private final int servings;

// 可选属性
private int calories = 0;


// 构造函数,设置必填属性
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}

// 设置可选属性的方法
public Builder calories(int val) {
this.calories = val;
return this;
}

// 构建最终的NutritionFacts对象
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
}

所有的字段都是 final 的,并且没有提供任何 setter 方法,对象一旦创建,其状态就不能再改变。

Builder 类用于构建 NutritionFacts 对象,Builder 类的构造函数接受必填属性。

  • Builder 类提供了链式调用的方法来设置可选属性。

最后,通过 build 方法创建 NutritionFacts 对象。

用私有构造器或者枚举类型强化Singleton属性

Singleton是指:仅仅需要被实例化一次的对象,通常用来代表没有状态的对象。

通过定义私有构造器,提供公有静态域或者工厂方法来获取单例对象。

私有构造方法可以保证调用方无法通过构造器获取对象,只能获取创建好的单例对象。

  • 为防止通过反射机制调用私有构造器,可以在第二次创建对象时抛出异常。

另一种实现单例的方法是:声明一个包含单个元素的枚举类型。

  • 这种方法提供了序列化机制,不用考虑单例对象在序列化和反序列化时需要做的额外工作。
1
2
3
public enum Singleton {
INSTANCE;
}

通过私有构造器强化不可实例的能力

一些工具类只是用来提供一些静态变量或者静态的工具方法,不希望被实例化。

  • 因为实例化没有意义。

但是在缺少显式声明的构造器时,编译器会自动提供一个无参的构造器,还是能被实例化和继承。

  • 正确的做法是提供一个私有的构造器,让这种类不能被继承,也不能被实例化。
1
2
3
public final class Arrays {  
private Arrays() {}
}
1
2
3
public class Collections {  
private Collections() {}
}

避免创建不必要的对象

下面这段代码,将变量sum声明为Long类型以后,每次在做加法操作时:

  • 会先将int类型的i转变为Long类型的实例,这将导致构建大量的Long实例。

要优先使用基本类型而不是对应的装箱类,防止无意识的自动装箱。

1
2
3
4
5
6
7
private static long sun() {  
Long sum = 0L;
for (int i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
}
return sum;
}

try-with-resources优先于try-finally

使用try-finally来关闭资源存在一些问题。

比如在try块和finally块中都抛出异常,try块中抛出的异常会被finally块中抛出的异常完全抹除。

  • 在异常堆栈轨迹中完全没有try块中的异常记录。

使用try-with-resources可以解决这个问题。

资源必须实现 java.lang.AutoCloseable 接口,这样才能在 try-with-resources 语句中使用。

  • try 块结束时,所有声明的资源都会自动关闭。
1
2
3
4
5
6
7
8
9
10
11
12
13
public class TryWithResourcesExample {
public static void main(String[] args) {
// 使用 try-with-resources 语句
try (BufferedReader br = new BufferedReader(new FileReader("example.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

try 语句的括号内声明资源,如果是多个资源,使用分号分割。

try 块中使用声明的资源,如果在 try 块中或资源关闭时发生 IOException,会捕获并处理异常。

如果处理资源或者关闭资源都发生了异常,后一个异常会被禁止,保留第一个异常,禁止的异常会被打印到堆栈轨迹中。

  • 也可以通过java.lang.Throwable#getSuppressed获取。