第2章 创建和销毁对象
何时以及如何创建对象,何时以及如何避免创建对象
如何确保适时销毁,如何管理对象销毁前必要的清理
第1条 考虑用静态工厂方法代替构造器
静态工厂方法是一个返回类的实例的静态方法,对它其实只是一个普通的静态方法而已,需要注意的是它与设计模式中的工厂方法不同,不要弄混淆了
Java源码中的例子:
public static Boolean valueOf(boolean b){
return b?Boolean.TRUE:Boolean.FALSE;
}
优势
列举静态工厂方法与构造器相比,有哪些优势
静态工厂方法有名称
构造方法没有名称,给静态工厂取一个适当的方法名称会更易于阅读,也更易于客户端使用.
特别是在面对有多个参数或者多个构造方法的类的时候,会让客户端不知道如何选择构造方法(自行脑补一个大写的懵B).
不必在每次调用它们的时候都创建一个新对象
我们知道构造方法每次都会创建一个新的实例,而当我们需要避免重复创建不必要的对象(或者不可变的类)时通过构造方法去做是做不到的,而静态方法可以为重复的调用返回相同的对象(比如返回的对象需要是一个单例)
它们可以返回原返回类型的任何子类型的对象
构造方法只能返回类本身,而静态方法可以返回它的子类,利用多态的特性使得静态方法更加灵活
比如Executors
类的各种newxxx
方法返回ExecutorService
的子类
在创建参数化类型实例的时候,它们使代码变得更加简洁
调用参数化构造器时,即使类型参数很明显也必须指明.
书中列举了实例化HashMap的例子:
Map<String,List<String>>m = new HashMap<Stirng,List<String>>();
如果用静态工厂可以这么写:
public static <K,V>HashMap<K,V>newInstance(){
reutrn new HashMap<K,V>();
}
想想以前也确实烦这些类型指定,一写一长串,但是现在的JVMS(Java1.7)已经实现了类型推导,所以其实也没那么复杂了.
劣势
静态工厂方法的劣势
类如果不含公有的或者受保护的构造器,就不能被子类化
它们与其他的静态方法实际上没有任何区别
确实,静态方法真的只是一个静态的方法而已,如果文档上不提到创建该类的实例可以使用静态方法的时候,很容易被忽略
所以能我们需要遵守一定的命名习惯:
静态方法推荐的命名名称
- valueOf 返回的实例与参数具有相同的值,一般用于类型转换(如String Boolean Double等等)
- of valueOf的简洁代替
- getInstance 返回的实例工具参数而定,对于单例,则一般没有参数,并且每次返回的都是同一个实例
- newInstance 每次返回一个新的实例
- getType
- newType
不能很好的扩展到大量的可选参数
当参数非常多的时候静态方法的可读性,维护性等就非常差了,这个后面会说
实际运用
现在在我们新建一个Fragment的时候,AS会自动生成一个newInstance(String ,String )
的一个方法,这个就是静态工厂方法了,可见官方也推荐我们使用静态工厂方法.
另外Java源码中也有非常多的静态工厂方法的运用,比如上面提及的Executors
,自己去体会~
小结
静态工厂方法与构造器各有优势,不过一般来说静态工厂通常更加合适,所以以后多考虑使用静态工厂吧.
第2条 遇到多个构造器参数时要考虑用构建器(Builder)
当实例化一个类时有很多可选参数的时候,我们或许会写很多不同参数的构造方法,可读性会非常差,并且客户端也不知道在什么时候用什么构造器,这是非常非常糟糕的,同样静态方工厂也不能避免,这个时候我们需要用到构建器,也即设计模式里的Builder模式
Builder模式非常常见了,列举个列子就过了:
AlertDialog dialog = new AlertDialog.Builder(this)
.setCancelable(true)
//...各种可选参数 想要什么设置什么
.setMessage("Builder模式")
.create();
Builder模式虽然能解决多参数遇到的问题,但是它也有缺点:
为了创建对象,我们必须先创建一个Builder,这是一个多余的开销,虽然开销并不明显,但确实存在.
小结
当有多个参数的时候,Builder模式是非常不错的选择.
第3条 用私有构造器或者枚举类型强化Singleton属性
所谓Singleton即单例,指仅仅被实例化一次的类,这一条与单例息息相关.
私有构造器强化
写过单例的一定知道,构造器一定要私有,否则别人随便new,怎么保证单例呢?
书中还讲到了单例如何防反射,防反序列化,这里就不提了
枚举强化
java 1.5 版本之后,单例多了一个实现方法(包含单个元素的枚举类型):
public enum Elvis{
INSTANCE;
public void leaveTheBuilding(){...}
}
优势:
- 无偿提供序列化机制
- 绝对防止多次实例化
- 防反射
- 简洁
书中说:单元素的枚举类型已经成为实现Singleton的最佳方法,可惜的是我没在实际中用过,也没见过这种实现方式.
小结
单例有很多实现方式,也是不容易的~
第4条 通过私有构造器强化不可实例化的能力
首先理解什么是不可实例化?
不可实例化是指只包含静态方法和静态域的类
比如BitmapUtil等各种Utils,它们包含各种静态方法,但是实际上我们不会也不需要去实例化它!
静态域(static-field) 比如:
public static Object obj
既然工具类不希望被实例化,那么如何做呢?
提供一个私有构造器,并在里面抛出异常即可
eg:
public class Util{
private Util{
// Suppress default constructor for noninstantiability
throw new AssertionError();
}
}
相信都会感觉这多此一举,谁tm没事去实例化工具类啊,以前我也这么觉得,直到我的膝盖中了...
偶不,直到我在优秀的库中看到这写法,比如RxJava中的Subscriptions
类:
public final class Subscriptions {
private Subscriptions() {
throw new IllegalStateException("No instances!");
}
//...
}
又如Jake
大神的RxBinding
中各种RxXXXX
也是如此:
private RxAppBarLayout() {
throw new AssertionError("No instances.");
}
优秀的项目,细节处理都非常优秀
小结
有时候有些东西非常有道理,只是自己太弱小,悟不到而已,虚心学习,keep growing
PS: EffectiveJava是本好书,RxJava是个优秀的库,Jake是真大神!!
第5条 避免创建不必要的对象
一般的讲,最后能重用对象而不是在每次需要的时候创建一个相同功能的新对象.
另外对于一个不可变的对象(immutable 后面会讲),它始终可以被重用.
作者举了几个例子来说明:
创建String实例
用String s = "stringette";
替代String s = new String("stringette")
因为后者的参数其实就是一个实例,每一次调用都会多创建一个没用的对象.
Boolean
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
Boolean.valueOf()方法重用了FALSE
和TRUE
来避免重复创建相同功能的对象
自动装箱autoboxing
自动装箱允许我们将基本类型和装箱基本类型(Boxed Primitive Type)混用,按需自动装箱和拆箱
它们俩之间性能是有明显的差别的(基本类型更优)
eg:
public static void main(String[] args){
Long sum = 0L;
for(long i=0;i<Integer.MAX_VALUE;i++){
sum += i;
}
System.out.println(sum);
}
sum 的类型为Long
,这样会比long
多创建约2的31次方的Long实例,影响性能,所需时间大约是long的6倍多,非常可怕啊
so,记住,优先使用基本类型!
PS 这里要说的不是创建对象非常昂贵,因为小对象的创建和回收是非常廉价的
对象池(object pool)
维护对象池来避免创建对象只针对非常重量级的对象,如数据库连接池
Android中对象池有很多,如Message
类,又如Glide中的Bitmap池
对象池的缺点
- 代码更乱 涉及到回收、重用必然会增加许多代码
- 增加内存占用,损害性能
小结
当需要重用的时候,就不要创建
第6条 消除过期的对象引用
首先需要明确,Java即使有GC,我们依然要自己考虑内存管理的事情.
当一个数组扩容后又缩减,比如size从0->200->100(一个栈先增长,后收缩),那么元素的index>=100的那些元素(被pop掉的)都算是过期元素,那些引用就是过期引用(永远不会再被解除的引用)
过期引用导致了内存泄露
虽然对于自己来说pop掉的元素我们不会去用,但是由于过期引用的存在,GC并不会去回收它们,所以需要我们手动清空这些引用.
eg:
public Object pop(){
if(size==0) throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; //Eliminate obsolete reference
return result;
}
第7条 避免使用终结方法
终结方法:finalizer
(老实说,这个真没用过)
没看懂,记录一些点..
- 终结方法会导致行为不稳定,降低性能,以及可移植性问题
- 不能保证会被及时地执行,而且根本不保证它们会被执行(这..好过分..)
还是没看懂,被自己蠢哭了..