Effective Java:创建和销毁对象

1. 静态工厂方法代替构造器

简介

获取类的实例,除了提供公有的构造器外,还可以使用静态工厂方法

静态工厂方法 提供实例,不同于设计模式中的 工厂方法模式,简单示例:

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

优势:

相对于构造器,静态工厂方法有如下优势

  • 他们有名称.如:
    //不需要用参数列表来表达语义,针对需要多个相同签名的构造器(调整构造器参数顺序也可达到目的)
    public boolean isProbablePrime(int certainty) {
        if (certainty <= 0) {
            return true;
        }
        return getBigInt().isPrime(certainty);
    }
  • 不是每次调用时都创建一个新对象.

针对创建对象代价很高的场景,类似于Flyweight模式.不可变类可以预先构建好实例,或者将构建好的实例缓存起来.

  • 可以返回原返回类型的任何子类型对象.

Collections类,返回隐藏的实现类,后续可更改实现,示例代码:Services.java

  • 创建更加简洁的参数化类型实例,如:
Map<String, List<String>> m = new HashMap<String, List<String>>();

可以精简为如下方式,虽然在java8中已经好太多.

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

缺点:

  • 如果不包含public/protect的构造器,就不能被子类化
  • 与其他静态方法实际上没有任何区别.因此需要用命名规范来弥补,如:
valueOf()
of()
getInstance
newInstance
getType
newType

2.多个参数考虑用builder

问题

无论是静态工厂还是构造器,对大量可选参数的扩展都不好.一向的办法:

  1. 使用重叠构造器模式,缺点是会给调用者带来疑惑.必须仔细小心,防止参数传递错误.
  2. 使用JavaBeans模式,缺点是设置参数被分到几个调用中,会导致JavaBean状态不一致,
    也就是说JavaBeans 阻止了把类做成不可变的可能

Builder:

示例:NutritionFacts.java

优点:

既保持了重叠构造器那样的安全性,也保证了JavaBeans模式那样好的阅读性.

  • Builder模拟了具名的可选参数.
  • 可以对其参数强加约束条件,在对象域中对参数进行检查.
  • 可以有多个可变参数
  • 可以用单个Builder创建多个对象

通常情况下,Builder 常与有限制的通配符(如Builder<? extends Node>)一起使用

public interface Builder<T>{
    public T build();
}

拓展

Java中 传统的抽象工厂实现 Class存在一些问题,newInstance充当了build的一部分,总是试图调用无参的构造函数(无论存不存在),
无参构造不存在也不会编译时错误,因此破坏了编译时的异常检查.而通过Builder就不会有这个问题.

缺点:

  • 必须先创建Builder的构建器,实践中的开销并不十分明显
  • 比重叠构造器模式更加的冗长,适合参数比较多的情况(且后期可能增加参数).

小结

  1. 针对构造器或静态工厂中有多个参数,且参数不固定的时候,Builder模式是不错的选择.
  2. 否则,用静态工厂,或者构造器会是更好的选择.

3.使用私有构造器或者枚举强化Singleton

1.5之前的两种方法

  • 私有构造器,公有静态成员

  • 私有构造器,公有静态方法

  1. 缺点: 这两种方式,可以通过反射机制(setAccessible)来进行破坏,可以在构造器创建第二个实例的时候抛出异常来避免.

  2. 优点: 工厂方法很灵活,可以不改变API,而改变类是否是Singleton

  3. 提醒: 为了防止反序列化创建新实例,需要声明实例域是transit的,并且提供readResolve方法,实例如下:

private static transient final Elvis INSTANCE = new Elvis();
private Object readResolve(){
    return INSTANCE;
}

枚举实现方式

示例: Elvis.java

Java 1.5 后可以通过单个元素的枚举实现单例,已经成为实现Singleton的最佳方式,有如下好处:

  • 绝对防止多次实例化

  • 防止反序列化共计

单元素的枚举类型 已经称为实现 Singleton最佳方法.

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

Utility不需要实例化,如果没有显示构造器,但是编译器会默认提供一个 公有/无参 的构造器.

通过抽象防止类被实例化是行不通的

  1. 这样该类就可以子类化,并且子类可实例化.
  2. 会误导用户,以为是专为继承而设计的

明智的做法是:

  1. 私有默认构造器,保证外部不可以访问
  2. 默认构造器抛出异常,避免类内部不小心调用.
public class UtilityClass {
    // Suppress default constructor for noninstantiability
    private UtilityClass() {
        throw new AssertionError();
    }
}

5.避免创建不必要的对象

概述

尽量重用对象,如果一个对象是不可变的,就可以始终被重用.

示例: Person.java

反面例子:

//每次都会创建一个新的"string"
String s = new String("string");

应该使用

String s = "string";

对于 同时提供了 静态工厂方法构造器的类,通常都是直接使用
静态工厂方法.

案例:

  • Map#keyset方法,对于一个给定的Map对象,每次调用都返回同一个Set实例.

  • 优先使用基本类型,而不是装箱类型

public static void main(String[] args) {
        Long sum = 0L;//这里每次都会构造一个实例,多创建了2^31个实例.
        for (long i = 0; i < Integer.MAX_VALUE; i++) {
            sum += i;
        }
        System.out.println(sum);
    }

小结

  1. 这里并不是说创建对象是十分昂贵的,如果能提高程序的清晰性,简洁性,多创建通常是好事.
  2. 除非Object pool中的对象是重量级的,否则无需维护自己的对象池.

6.消除过期对象引用

示例:

//class Stack
public Object pop(){
    if(size ==0){
        throw new EmptyStackException();
    }
    Object result= elements[size--];
    elements[size] = null;//清空过期引用
    return result;
}

清空过期引用的好处,如果再次引用,将抛出NullPointerException

说明

  1. 消除过期引用最好的方法是: 让 包含该引用的变量结束期生命周期.(自然发生).
  2. 但上面的例子中显然 不是result来管理 elements[size]生命周期,而是外层类 Stack

提醒

内存泄漏来源

  1. 一般如果类自己管理内存,就需要警惕内存泄漏问题.
  2. 另一个常见来源是 缓存.解决办法:使用 WeakHashMap.[常见的迷惑 就是 不知道缓存是否还有意义(生命周期).]
  3. 最后,就是监听器和其他回调.解决办法: 使用弱引用( Weak reference)

7.避免使用终结方法

终结方法的缺点

终结方法(finalizer)是不可预测的,危险的,一般也是不必要的.根据经验,应尽量避免使用终结方法

  1. JVM 会延迟执行 终结方法.导致从对象不可达到终结方法执行时间不可控.
  2. Java语言规范不仅不保证终结方法及时执行,而且不保证他们会被执行.

不能依赖 终结方法来更新重要的持久状态.
System.gcSystem.runFinalization只是增加了终结方法被执行的机会.
System.runFinalizersOnExitRuntime.runFinalizersOnExit是声称可保证终结方法一定被执行,因有严重缺陷而废弃/

  1. 使用终结方法,有一个严重的Severe性能损失.

终结方案:

  • 提供一个显示的终止方法.在对象不可用的时候,调用即可.典型例证:InputStream,OutputStream
  • 显示的终止方法,通常与try-finally结合使用,以确保及时执行.
  Foo foo = new Foo();
  try{
    //...
  }finally{
    foo.terminate();//
  }

终结方法的好处

  • 当 所有者 忘记调动显示 终结方法时,终结方法可以充当安全网(释放关键资源总比一点都不释放要好)
  • 第二种用途 与 java本地对等体(native peer) 相关.[本地对等体是一个本地对象,普通对象通过本地方法委托给一个本地对象].本地对等体不是一个普通对象,垃圾回收器并不知道它.当它的Java对等体被回收的时候,它不会被回收.
  • 终止方法链并不会被自动执行,如果子类有并覆盖了终结方法.就必须手动调用父类的终结方法
  @Override protected void finalize() throws Throwable {
    try {
    //即使子类抛出异常,父类的终结方法也会执行
      //finalize sub class
    } finally {
      super.finalize();
    }
  }

提醒

要防止忘记调用超类的终结方法,可以使用一个终结方法守卫者,如下:

public class Foo{
    private final Object finalizer = new Object(){
         @Override protected void finalize() throws Throwable {
            Foo.this.finalize();
          }
    }
}

小结

  1. 除非作为安全网,或者终止非关键的本地资源,否则不要使用本地方法/
  2. 既然使用了终结方法,就必须调用父类的终结方法.
  3. 如果要把终结方法公有的非 final类 关联起来,需要使用 终结方法守卫者
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值