学着写了Markdown,感觉挺舒服,这一篇笔记我也改成md格式的好了。
创建和销毁对象
1. 考虑用静态工厂方法代替构造器
问题:面对构造器重载的情况
本条优点:
- 静态工厂方法有函数名,可以表明这个函数的工厂方法的具体作用,这比单纯的构造器要具体
- 不必每次创造新的实例,静态工厂方法可以对实例进行缓存,重复使用,就像缓存池一样
- 可以返回一个类的子类,工厂方法的返回值可以自定,既返回一个继承了目标类的子类或者扩展了接口的类也是可以的
- 可以使代码简洁,省略一些重复的,显而易见的参数
本条缺点:
- 针对优点的3,如果目标类没有公有的或者保护的构造器,那么目标类不能被继承,也就谈不上返回一个目标类的子类了
- 本身与普通静态方法没有区别,没有和构造器一样显著的地位,这使得使用者并不能一眼在使用文档看到使用静态工厂方法生成实例的方法,同时JavaDoc工具也不能生成区别一般方法的静态构造方法的文档。
Java中实践这一条的类:
- java.util.EnumSet类,没有公有构造器,只有静态工厂方法,并且实践了优点中的第3条:当枚举个数小于等于64时返回RegalarEnumSet,大于64时返回JumboEnumset,而这对客户代码并不可见。
2. 遇到多个构造器参数时要考虑用构建器:
问题:一个类有一些构造时一定要填写的属性和一些可选的属性
以前的解决方法:
- 使用重叠构造器的方法,首先编写只包含必选属性的构造器,然后一层层包装可选属性的构造器
缺点:调用时程序员对各个参数并了解、容易写串
- 使用重叠构造器的方法,首先编写只包含必选属性的构造器,然后一层层包装可选属性的构造器
- 使用JavaBeans模式,设定一系列的setter方法
缺点:阻止把类做成不可变的可能性、类的初始化不是原子的,在使用过程中可能存在不一致的状态 本条建议: 使用静态内部类,通过在静态内部类中调用一系列的setter方法填充内部类的属性,最后调用类的构造器,传入一个静态内部类的实例构造一个外部类实例
在java1.5以后,有接口Build ,静态内部类扩展了这个接口后,外部类构造还可以直接传入一个Build实例,进而这实现了抽象工厂模式。
本条优点:
- 保证了重叠构造器的安全性和JavaBeans模式的良好可读性
- 传统的Java抽象工厂是实现Class对象,newInstance充当了build方法的一部分,但是newInstance使用时会调用无参构造函数,如果类没有实现无参构造函数的话,会抛出异常,而不是编译时错误。Build接口没有这个问题
- 本条缺点:
- 性能不够好
- 代码较重叠构造器而言更冗长
- 代码:
public class NutritionFacts{
private final int servintSize;//必须
private final int servings;//必须
private final int calories;//选填
private final int fat;//选填
private final int sodium;//选填
private final int carbohydrate;//选填
//静态内部类
public static class Build implements Build{
//必须
private final int servintSize;
private final int servings;
//选填 赋以默认值
private final int calories=0;
private final int fat=0;
private final int sodium=0;
private final int carbohydrate=0;
public Builder(int servintSize,int servings){
this.servintSize=servintSize;
this.servings=servings;
}
//Setter方法,总是返回this,这样可以使用级联
public Builder calories(int val){
this.calories=val;
return this;
}
public Builder fat(int val){
this.fat=val;
return this;
}
public Builder sodium(int val){
this.sodium=val;
return this;
}
public Builder carbohydrate(int val){
this.carbohydrate=val;
return this;
}
@Override
public NutritionFacts bulid(){
return new NutritionFacts(this);
}
}
//外部类的构造函数
private NutritionFacts(Builder builder){
this.servintSize=bulider.servintSize;
this.servings=bulider.servings;
this.calories=builder.calories;
this.fat=bulider.fat;
this.sodium=builder.sodium;
this.carbohydrate=bulider.carbohydrate;
}
}
//调用时的书写方式:
NutritionFacts cocaCola=new NutritionFacts.Builder(240,8).calories(100).sodium(35).carbohydrate(27).bulid();
3. 用私有构造器或者枚举类型强化Singleton属性
问题:创建一个单例的类
以前的方法:
使用私有构造器和公有的静态final域实例化一个实例(因为静态域类的所有实例公用一个)
- 缺点:不能防止反射,通过setAccessible可以调用私有构造器
- 代码:
public class Elvis{ public static final Elvis INSTANCE=new Elvis(); //私有构造器 private Elvis(){...} pubic void leaveTheBuilding(){...} }
使用私有构造器、公有的静态final域实例化一个实例并且使用公有的静态工厂方法
- 优点:灵活,可以随时更改内部实现
- 缺点:依旧可以通过反射调用私有构造器、序列化困难,需要加入方法readResolve方法//见77条
- 代码:
public class Elvis{ public static final Elvis INSTANCE=new Elvis(); //私有构造器 private Elvis(){...} public static Elvis getInstance(){ return INSTANCE; } pubic void leaveTheBuilding(){...} } //使用时调用getInstance Elvis.getInstance();
本条优点:简洁、本身就可以序列化、没有反射导致的问题
代码:
public enum Elvis{ INSTANCE; public void leaveTheBuilding(){...} }
背景知识:Java中枚举在编译过程中内部也是生成了类的,这个类继承了java.lang.Enum的类,因而枚举中可以定义自定义的属性、方法,但是不能再继承其他的类了,不过可以扩展接口
4. 通过私有构造器强化不可实例化的类的能力
问题:一些工具类,它们本身是静态的,它们的方法是静态的,也就是说,它们仅仅是提供一些公有的处理方法,但是这些类不正当使用下,可以被实例化,这并没有什么用。
以前的方法:
- 通过将类设计成抽象类
- 缺点:可以被继承,依旧有被子类实例化的可能
- 通过将类设计成抽象类
本条优点:不可被实例化,甚至可以在私有构造器中抛出异常,这使得只要即使在类的内部调用构造器都不被允许
本条缺点:不可被子类化
5. 避免创建不必要的对象
问题:在一些代码中存在重复创造对象的现象。比如:每创建一个类的实例就初始化一个不变化的对象、使用包装类等
本条建议:
- 抽离每次实例化时不改变的对象,用静态私有对象替代
- 减少包装类的使用
- 创建小对象代价并不昂贵,相反,维护自己的对象池来避免创建对象并不总是一个好做法。
本条优点:性能提升。
6. 消除过期的对象引用
问题:当类自己管理内存是,可能出现因为没有对对象的引用及时置为null,而引起内存溢出
本条建议:
- 清空对象的引用,当然,这应当是一种例外,而不是规范,多数时候,JVM会处理好。
背景知识:内存泄漏三种原因:
- 类自己管理内存
- 缓存–应对方法:使用WeakHashMap以及弱引用
- 监听器和回掉方法–应对方法同上
7. 避免使用终结方法(finalizer)
问题:一些代码有将资源释放的代码块放到终结方法中的现象
- 因为终结方法不等同于C++的析构函数,方法优先级低,延时很高,甚至有些时候存在不执行的问题,
- 同时,在终结方法中的代码如果运行时出错,将不会抛出任何异常信息,这同样带来了调试的困难,
- 使用终结方法还有降低性能的问题,
- 存在子类集成了父类的方法,但是在子类的终结方法中没有显示调用父类的终结方法的问题(继承中终结方法需要显示调用)
终结方法的正确用途:
- 终结方法当作安全网使用,即以防万一,在使用了上一条的建议后进而编写终结方法。
- 充当本地对等体,本地对等体是一个本地对象,普通对象通过本地方法委托给一个本地对象,垃圾回收不会回收本地对等体//这段没有懂…
本条建议:
- 使用显示的资源关闭方法,要求这个方法中使用try-finally代码块,保证资源一定关闭。同时,要有一个私有的标识符表明当前资源关闭方法是否调用过。
- 使用终结方法守卫者,即在父类中使用一个final的内部匿名类,这个匿名类的唯一用途就是在自己内部调用外部类的终结方法,这使得即便父类被子类化并且子类没有显式调用父类的终结方法也不会出现问题。