Effective Java
第2章 创建和销毁对象
第1条 静态工厂方法代替构造类的优势
- 静态工厂方法有名称 ,见名知意,调用时更为直接明了。
- 调用静态工厂方法创建对象时不必每次都创建新对象 ,例如单例模式,享元模式,对一个无需更改属性的对象重复利用,减少资源开销。
- 可以返回原返回类型的任意子类对象 ,即父类引用指向子类对象,更灵活的选择返回对象的类。
- 可以根据参数值更改所返回的对象的类 。
- 方法返回的对象所属的类在编写该静态工厂方法时可以不存在。 例如JDBC中的驱动接口,实现类可以有mysql 、Oracle等。
第2条 构建器
2.1 使用构建器的原因
1. 创建的对象参数较多,重叠构造器模式客户端代码较难编写,并且难以阅读。
2. JavaBeans 模式线程不安全,多个调用过程中,JavaBean可能处于不一致状态,并且JavaBeans 模式使得类不可变的可能性不复存在。
3. Build模式也适用于类层次结构
2.2 建造者(Build)模式举例
package liukang.effective.d20210123;
public class Dog {
private int age;
private String hairColor;
private String character;
public static class Builder{
private int age = 5;
private String hairColor = "black";
private String character;
public Builder age(int age){
this.age = age;
return this;
}
public Builder hairColor(String hairColor){
this.hairColor = hairColor;
return this;
}
public Builder character(String character){
this.character = character;
return this;
}
public Dog build(){
return new Dog(this);
}
}
public Dog(Builder builder){
this.age = builder.age;
this.hairColor = builder.hairColor;
this.character = builder.character;
}
@Override
public String toString() {
return "Dog{" +
"age=" + age +
", hairColor='" + hairColor + '\'' +
", character='" + character + '\'' +
'}';
}
public static void main(String[] args) {
Dog dog = new Builder().age(6).hairColor("black").character("暴躁").build();
System.out.println(dog);
}
}
第3条 用私有构造器或枚举类型强化Singleton属性
3.1 前提
- 构造器私有。
- 提供访问该类 唯一实例的方式。
3.2 单元素的枚举类经常成为实现SingleTon的最佳方法。
3.3 实现SingleTon的五种方式
- 懒汉式 分线程安全与不安全。
package liukang.effective.d20210123;
public class SingleTon {
public static SingleTon singleTon;
private SingleTon(){}
public static SingleTon getSingleTon(){
if (null == singleTon){
singleTon = new SingleTon();
}
return singleTon;
}
}
package liukang.effective.d20210123;
public class SingleTon {
public static SingleTon singleTon;
private SingleTon(){}
public static SingleTon getSingleTon(){
if (null == singleTon){
synchronized (""){
if (null == singleTon){
singleTon = new SingleTon();
}
}
}
return singleTon;
}
}
- 饿汉式。
package liukang.effective.d20210123;
public class SingleTon {
private static SingleTon singleTon = new SingleTon();
private SingleTon() {
}
public static SingleTon getSingleTon() {
return singleTon;
}
}
- 静态内部类。
package liukang.effective.d20210123;
public class SingleTon {
private SingleTon() {
}
private static class InnerSingleTon{
private static SingleTon singleTon = new SingleTon();
}
public static SingleTon getSingleTon(){
return InnerSingleTon.singleTon;
}
}
- 枚举。
package liukang.effective.d20210123;
public enum SingleTon {
single;
}
- 补充
众所周知,单例模式是创建型模式,都会新建一个实例。那么一个重要的问题就是反序列化。当实例被写入到文件到反序列化成实例时,我们需要重写readResolve方法,以让实例唯一。
private Object readResolve() throws ObjectStreamException {
return singleton;
}
第4条 通过私有构造器强化不可实例化能力
- 将类做成抽象类来强制该类不可实例化是不可行的。
原因:抽象类的子类以及子类的子类都可以被实例化。 - 让这个类包含一个显示的私有的构造器即可以实现此类不可被实例化的属性。
第5条 优先考虑依赖注入来引用资源
- 静态工具类和SingleTon类不适合于需要引用底层资源的类。
- 不要用这个类来创建这些资源。
- 应该将这些资源或者工厂传给构造器(或者静态工厂,或者构建器),通过他们来创建类。
- 依赖注入极大的提升了类的灵活性、可重用性和可测试性。
第6条 避免创建不必要的对象
- 举例
public static boolean matches(String regex, CharSequence input) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(input);
return m.matches();
}
- 自动装箱使得基本类型和装箱基本类型之间的差别变得模糊起来,但并没有完全消除,要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱。
第7条 消除过期的对象引用
- 清空对象应该是一种例外,而不是一种规范行为。
- 一般来说,只要是类自己管理内存,程序员就应该警惕内存泄漏问题。
- 内存泄漏的另一个常见来源是缓存。
WeakHahMap: 此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值,所以比较适合做缓存。
- 内存泄露的第三个常见来源是监听器和其他回调。
第8条 避免使用终结方法和清除方法
- 终结方法和清除方法不能保证会被及时执行。
- 注重时间的任务不应该由终结方法来执行。
- 终结方法线程的优先级比该应用程序的其他线程的优先级要低得多。
- 如果忽略在终结过程中被抛出来的未捕获的异常,该对象的终结过程也会终止,即此对象并没有顺利被GC,此时如果另一个线程企图使用这种被破坏的对象,则可能发生任何不确定的行为。
- 使用终结方法和清除方法有非常严重的性能损失。
- 终结方法有一个严重的安全问题:如果从构造器或者它的序列化对等体抛出异常,恶意子类的终结方法就可以在构造了一部分的应该已经半途夭折的对象上运行。
- 为了防止非final类受到终结方法的攻击,要编写一个空的finalize方法。
- 如果类的对象中封装的资源(例如文件或者线程)确实需要终止,只需让类实现AutoCloseable,并要求其客户端在每个实例不再需要的时候调用close方法,一般是利用try-with-resources确保终止,即使遇到异常也是如此。
第9条 try-with-resources 优先于try-finally
- 在处理必须关闭的资源时,始终要优先考虑用try-with-resources,而不用try-finally,这样得到的代码将更加简洁、清晰,产生的异常也更有价值。