EffectiveJava-1-创建和销毁对象
一. 使用静态工厂方法代替构造器
1. 一个类对外提供获取自身实例对象的方法:
- 提供公有构造器;
- 公有的静态工厂方法(一个返回当前类实例的静态方法,包括当不限于我们平时所写的单例);
2. 静态工厂方法的优势 :
a. 可以随意组合要初始化的属性,通过不同的命名,不仅可以避免构造器的限制(一个类只能有一个带有指定签名的构造器),而且可以做到顾名思义;
b. 可以不用在每次调用时都创建一个新的对象,可以使用预先构建好的对象,或将构建好的对象缓存起来,进行重复利用,适用于经常请求创建相同对象,并且创建对象的代价很高,如常见的单例模式写法就是对这一点的应用;实例受控的类:能为重复的调用返回相同的类,有助于类控制某个时刻哪些实例应该存在,功能如下:
1.确保它是一个Singleton或者是不可实例化的;
2.使得不可变的类不会存在两个相等的实例,即当切仅当a==b时,才有a.equals(b)=true,如果保证了这一点, 就可以使用==代替equals,这样可以提高性能,枚举类型保证了这一点 ;
(==为hash地址相同,即同一个对象;equals为实例内容相同)
c. 可以返回原返回值类型的任何子类型的对象,有更好的灵活性,而且可以隐藏实现类(子类),使API更简洁,适用于基于接口的框架;
(如可以用工厂方法的不同命名标识不同的子类对象,也可以同一个工厂方法,通过参数(如Type)进行判断,返回不同类型的子类对象)
d. 创建参数化类型实例(即泛型)时,使代码更简洁,如:
3. 静态工厂方法的缺点
a. 类如果不含公有的(public)或受保护的(protected)构造器,就不能被子类化,不方便扩展和形成关系体系;
(不过某些情况也可以用组合代替继承,同样可以实现代码的复用)
(组合:即该类的实例做为新类的一个属性)
b. 与其他静态方法没有任何区别,不方便使用者查找使用;
(可以通过添加注释以及命名规范,弥补这个问题)
4. 实例演示:构造器与静态工厂方法
5. 之前有自学过iOS开发,静态工厂获取实例的方式,在Object-c中深有体现,只是他们叫做类工厂方法,就像下面代码这样:
二. 遇到多个构造器参数时要考虑使用构建器(建造者模式)
如果有大量的可选参数需要任意组合,我们来想想看有哪些实现方案呢?
1. 使用构造器:不能任意的组合;
2. 使用公有静态工厂方法:需要提前写出足够多组合的工厂方法,而且参数过多时通过命名区分也将很不方便;
3. 使用JavaBeans:即无参构造+setter方法,但是这使得构造过程分到了多个调用中,而且不能有效的保证一致性,还有就是阻止了把类做成不可变的可能,需要额外保证它的线程安全性;
4. 使用Builder模式:
优点:既保证了安全性,又有很好的可读性,而且方便对参数添加约束条件,也可以自动填充某些参数而不提供给调用者去修改(即对客户端隐藏一些属性);
缺点:代码量增多,可能增加内存开销;
适用于参数比较多的情况,以上面所用对Person类为例;
通过建造者创建实例:
三. 用私有构造器或者枚举类型强化Singleton属性
- 实例受控;
- 单例模式的饿汉式,懒汉式,都是需要将构造器私有化;
- 单元素的枚举类型已经成为实现 Singleton 的最佳方法;
四. 通过私有构造器强化不可实例化的能力
有些类只有静态方法和静态属性,如一些工具类,比如自定义日志工具类LogUtil,我们不希望它可以创建实例,然而一个类在缺少构造器时,系统会默认提供一个公有无参的构造方法,这时我们可以显示的提供一个私有的构造方法,以保证该类不可实例化;
五. 避免创建不必要的对象
例1:将字符串转为boolean类型的实现方法,使用Boolean.valueOf("true")要优于new Boolean("true"),其源码内部实现如下:
例2:还是以上面的Person类为例,现在新增加一个判断是否生育高峰期出生的方法;
注意这里是说避免创建不必要的对象,而不是尽量少的创建对象;
六. 消除过期的对象引用
一般而言,只要类是自己管理内存,就应当注意内存泄漏问题;
例如:栈实现类ArrayStack,当栈先增长后收缩,栈内还维护着过期引用,这种情况称为无意识的对象保持,那么就需要在pop()方法中将已经出栈的元素设为空;
内存泄漏另一个常见来源是缓存,有下面几种解决方案:
1. 使用WeakHashMap代表缓存,缓存中的项过期后就会自动被删除;
2. 使用后台线程定时的清除没用的缓存,或者添加新的缓存时进行清除,使用LinkedHashMap.removeEldestEntry()就可以实现后一种方案;
3. 对于更加复杂的缓存,必须直接使用 java.lang.ref;
4. 监听器和其他回调:要取消注册或使用weak reference,如只将他们保存为WeakHashMap中的键;
七. 避免使用终结方法
finalizer通常是不可预测的,也是很危险的,一般是不必要的,除非作为安全网,或者为了终止非关键的本地资源,若使用了终结方法,就要记住调用super.finalize,如果需要把终结方法与公有的非final类关联起来,请考虑使用终结方法守卫者,以确保即使子类的终结方法未能调用super.finalize, 该终结方法也会被执行;