写点什么

EffectiveJava 读书笔记 -01- 对象创建与销毁

用户头像
wander
关注
发布于: 2020 年 10 月 09 日

一、静态工厂方法替代构造器

静态工厂方法是一个返回类的实例的静态方法。

优势:

  1. 静态工厂方法有名称,构造器的参数本身没有确切的描述被返回对象。

  2. 不必每次调用的时候都创建一个对象。

  3. 可以返回原返回类型的任何子类。

  4. 返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值。

  5. 返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在。(如jdbc的ConnectionManager.getConnection,通过Class.forName("com.mysql.jdbc.Driver")来加载jdbc驱动,在编写getConnection方法时是无法感知数据库类型的)


缺点:

  1. 类如果只有私有化构造器,就不能通过构造器被实例化。

  2. 静态工厂方法很难被程序员发现。通常通过命名规则来避免这种劣势。

规则:from-----类型转换方法,只有单个参数,返回该类型的一个实例。

Date.from(instant)

of--------聚合方法,带有多个参数,返回该类型的一个实例。

valueOf--比from和of更繁琐的一种替代方法。

create/newInstance--确保每次调用都返回一个新的实例

getType--在工厂方法处于不同的类中的时候使用。type表示工厂方法所返回的对象的类型。

newType--像newInstance一样,在工厂方法处于不同的类中的时候使用。

二、遇到多个构造方法参数时要考虑使用构造器模式

静态工厂和构造方法有个共同的局限性,它们都不能很好地扩展到大量的可选参数。

例子:

一个表示食品包装外面显示的营养成分的标签:总脂肪量、饱和脂肪量、转化脂肪、胆固醇、钠等等
public class NutritionFacts{
private final int servingSize;
private final int servings;
private final int calories;
private final int sodium;
private final int carbohydrate;
}

该类有如下构造方法:

public NutritionFacts(int servingSize,int servings,int calories)
public NutritionFacts(int servingSize,int servings,int calories,int sodium)
public NutritionFacts(int servingSize,int servings,int calories,int sodium,int carbohydrate)

当我们构建一个可口可乐包装营养成分标签时:

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

你必须要设置标签列表中所有属性的值,如果由更多的参数,使得构建类变得非常复杂。此时,可能会考虑到JavaBean的getter和setter方法

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

看上去构造类更直观的,遗憾的是,JavaBean模式很难保证对象的一致性。可能cocaCola类未初始化完就被其他类调用。此时,用构造器模式可以很好的解决这个问题。

public class NutritionFacts{
private final int servingSize;
private final int servings;
private final int calories;
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 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 calories;
}
public Builder fat(int val){
fat = val;
}
public Builder sodium(int val){
sodium = val;
}
public Builder carbohydrate(int val){
carbohydrate = val;
}
public NutritionFacts build(){
retrun new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder){
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}

客户端调用代码:

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

这样客户端代码易于阅读,可以在builder的构造器中检查参数的有效性。

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

Singleton类是指仅仅被实例化一次的类。要实现这个目的有两种方式。

1、保持构造器为私有,共有静态成员是一个final域:

public class Elvis{
public static final Elvis INSTANCE = new Elvis();
private Elvis(){
}
}

虽然构造方法时私有的,但是,仍然可以通过反射setAccessible(true)来访问构造方法。如果要抵御这种攻击,可以修改构造器,在返回第二个实例时抛出异常。

2、保持构造器私有,共有的成员是个静态工厂方法:

public class Elvis{
public static final Elvis INSTANCE = new Elvis();
private Elvis(){
}
public static Elvis getInstance(){
return INSTANCE;
}
}

共有域方法的主要优势在于,API很清楚的表明了这个类是一个Singleton。

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

有时候可能需要编写只包含静态方法和静态域的类,如java.lang.Math、java.util.Arrays等,这样的工具类不希望被实例化,因为实例化他们没有任何意义。让这个类包含一个私有构造器,它就不能被实例化。

public class UtilityClass{
private UtilityClass(){}
}

五、优先考虑依赖注入来引用资源

有许多类会依赖一个或多个底层的资源。如拼写检查器需要依赖字典。

public class SpellChecker{
public static final Lexicon dictionary = ...;
private SpellChecker(){}
public static isValid(String word){...}
}

在实例化拼写检查器时,我们必须实例化一个字典,而不同的语言有不同的字典,如果我们在编译期间无法确定字典,程序将无法正常运行。

public class SpellChecker{
public static final Lexicon dictionary = ...;
public SpellChecker(Lexicon dictionary){
this.dictionary = dictionary;
}
public static isValid(String word){...}
}

当实例化一个拼写检查器实例时,就将字典通过构造器注入到拼写检查器中了。依赖注入同样适用于构造器模式、静态工厂。虽然依赖注入提升了项目的灵活性和可测试性,但是在大型项目中,会导致类的组织混乱,因为一个项目通常包含上千个依赖,这些类之间的相互依赖使得项目变得凌乱不堪。引入依赖注入框架,可以很好的解决这个问题。长见的依赖注入框架:Dagger、Guice、Spring。

六、避免创建不必要的对象

一般来说,最好能重用对象,而不是每次都需要的时候就创建一个功能相同的新对象。

String s = new String("hello");

因为“hello”本来就是一个String实例,new String()又多构建了一个新的实例,这样做是没必要的。正确的方式应该是:

String s = "hello";

有些对象的创建成本比其他对象要高得多,比如Person对象创建时要从数据库(数据量亿级规模)获取属性值,建议缓存下来重用。



发布于: 2020 年 10 月 09 日阅读数: 52
用户头像

wander

关注

还未添加个人签名 2020.10.07 加入

还未添加个人简介

评论

发布
暂无评论
EffectiveJava读书笔记-01-对象创建与销毁