读书笔记——1.创建和销毁对象

hardwork
Java从诞生到日趋完善,经过了不断发展日益强大,目前全世界拥有了成千上万的Java开发人员。如何编写出更清晰、更正确、更健壮且更易于重用的代码,是所有编程开发人员所追求的目标之一。

如果你现在已经在Java开发方面有了一定的经验,而且想更加深入地了解Java编程语言,成为一名更优秀、更高效的Java开发人员,那么,建议你用心研读本书。

本文是关于《Effective Java》的读书笔记。

Questions

  • 何时以及如何创建对象?
  • 何时以及如何避免创建对象?
  • 如何确保它们能够适时地销毁?
  • 如何管理对象销毁之前必须进行的各种清理动作?

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

什么是静态工厂方法?

如下有一个Boolean类中的valueOf方法,这个方法将boolean原始类型转换成了一个Boolean对象的引用。

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}

静态工厂方法与构造器不同的第一大优势在于,它们有名称。

如果构造器的参数本身没有确切地描述正被返回的对象,那么具有适当名称的静态工厂会更容易使用,产生的客户代码也更易阅读。

静态工厂方法与构造器不同的第二大优势在于,不必在每次调用它们的时候都创建一个新的对象。

静态工厂方法能够为重复的调用返回相同的对象,这样有助于类总能严格控制在某个时刻哪些实例应该存在。

这种类被称作实例受控的类(instance-controlled)。

实例受控的类可以确保它是一个Singleton或者是不可实例化的。

它还确保不会存在两个相等的实例,即当且仅当a==b时才有a.equals(b)为true。如果类保证了这一点,它的客户端就可以使用==操作符来代替equals()方法,这样可以提高性能

枚举类型就保证了这一点。

静态工厂方法与构造器不同的第三大优势在于,它们可以返回原返回类型的任何子类型的对象。

这样我们在选择返回对象的类型时就有了更大的灵活性。

参考如下示例:

// Service interface
public interface Service {
    ...
}

// Service provider interface
public inteface Provider {
    Service newService();
}

// Noninstantiable class for service registration and access
public class Services {
    private Services() {}    // Prevents instantiation

    // Maps service names to services
    private static final Map<String, Provider> providers =
        new ConcurrentHashMap<String, Provider>();
    public static final String DEFAULT_PROVIDER_NAME = "<def>";

    // Provider registration API
    public static void regiserDefaultProvider(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<String, List<String>> m = 
    new HashMap<String, List<String>>();

以上代码让人觉得很繁琐,假设HashMap提供了如下的静态工厂:

public static <K, V> HashMap<K, V> newInstance() {
    return new HashMap<K, V>();
}

这样就用如下代码代替前面繁琐的声明:

Map<String, List<String>> m = HashMap.newInstance();

到目前为止jdk1.8,标准的集合实现如HashMap并没有工厂方法,但是你可以把这些方法放在自己的工具类中。更重要的是,可以把这样的静态工厂放在你自己的参数化类中。

但是在jdk1.7提供了简便的泛型声明,可以使用如下代码代替前面的声明:

Map<String, List<String>> m = new HashMap<>();

静态工厂方法与构造器不同的主要缺点在于,类如果不含共有的或者受保护的构造器,就不能被继承。

因为静态工厂方法一般都需要将构造器私有化,所以它鼓励使用复合(composition)而不是继承。

静态工厂方法与构造器不同的第二个缺点在于,它们与其它的静态方法实际上没有任何区别。

下面是静态工厂方法的一些惯用名称:

  • valueOf
  • of
  • getInstance
  • newInstance
  • getType
  • newType

总结

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

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

考虑一下用一个类表示包装食品外面显示的营养成分标签。这些标签中的几个域是必需的:每份的含量、每罐的含量以及每份的卡路里,还有超过20个可选域:总脂肪量、饱和脂肪量、转化脂肪、胆固醇、钠等等。大多数产品在某几个可选域中都会有非零的值。

以下采用向来习惯的重叠构造器(telescoping constructor)模式描述:

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
// 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,
int calories, int fat, int sodium, int carbohydrate)
{

this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}

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)
{

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

public NutritionFacts(int servingSize, int servings,
int calories)
{

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

public NutritionFacts(int servingSize, int servings) {
this(servingSize,servings,0);
}
}

当你想要创建实例的时候,就利用参数列表最短的的构造器,但该列表中包含了要设置的所有参数:

NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);

如果“仅仅”是这6个参数,看起来还不算太糟,问题是随着参数数目的增加,它很快就失去了控制。

一句话总结:重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然较难以阅读。

遇到许多构造器参数时,可以选用第二种方案——JavaBean模式,在这种模式下调用一个无参构造器来创建对象,然后调用setter方法来设置每个必要的参数,以及每个相关的可选参数:

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
// JavaBean Pattern - allows inconsistency, mandates mutability
public class NutritionFacts {
// Parameters initialized to default values (if any)
private int servingSize = -1; // Required; no default value
private int servings = -1; // Required; no default value
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;

public NutritionFacts(){}
// Setters
public void setServingSize(int servingSize) {
this.servingSize = servingSize;
}
public void setServings(int servings) {
this.servings = servings;
}
public void setCalories(int calories) {
this.calories = calories;
}
public void setFat(int fat) {
this.fat = fat;
}
public void setSodium(int sodium) {
this.sodium = sodium;
}
public void setCarbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
}
}

这种模式弥补了重叠构造器模式的不足——在创建实例时很容易,这样的代码也易于阅读:

NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);

JavaBean模式自身具有的缺点:
1. 因为构造过程被分到几个调用中,在构造过程中JavaBean可能处于不一致状态。类无法仅仅通过检验构造器参数的有效性来保证一致性。试图使用处于不一致状态的对象,将会导致失败,这种失败与包含错误的代码大相径庭,因此它调试起来十分困难。

2. JavaBean模式阻止了把类做成不可变的可能,这就需要程序员付出额外的努力来确保它的线程安全。

幸运的是,还有第三种方案可供选择。既能保证像重叠构造器模式那样的安全性,也能保证像JavaBean模式那样的可读性。这就是Builder模式的一种形式。不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builder对象。然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后,客户端调用无参的build方法来生成不可变的对象。这个builder是它构建的类的静态成员类:

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
54
55
56
57
58
59
// Builder Pattern
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;

public 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 carbohydrate = 0;
private int sodium = 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 carbohydrate(int val) {
carbohydrate = val;
return this;
}

public Builder sodium(int val) {
sodium = val;
return this;
}

public NutritionFacts build() {
return new NutritionFacts(this);
}
}

public NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}

客户端代码:

NutritionFacts cocaCola = new NutritionFacts.Builder(240,8).
    calories(100).sodium(35).carbohydrate(27).build();

总结:如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是种不错的选择,特别是当大多数参数都是可选的时候。

坚持原创技术分享,您的支持将鼓励我继续创作