创建和销毁对象
重点关注对象的创建和销毁:什么时候、如何创建对象,什么时候、什么条件下应该避免创建对象,如何保证对象在合适的方式下被销毁,如何在销毁对象之前操作一些必须的清理行为。
尝试用静态工厂方法代替构造器
如果一个
client
要实例化一个对象来使用,傻
b
都知道应该先调用类的构造器来
new
一个对象,之后再调用相应的方法。除了这个方式,
Java Effective
还建议了另一种方法:用静态工厂方法来提供一个类的实例。以下的例子不反映两者的优劣,只是反映两者在代码实现上的不同,优劣之后再谈:
假设咱们要一个颜色为黑色、长度为
50cm
的锤子,自然就用构造器创建一个
Hammer myHammer = new Hammer(Color.BLACK, 50);
而用静态工厂方法来实例化一个对象,如下
Hammer myHammer = Hammer.factory(Color.BLACK,50);
也可以用专门的一个工厂类来实例化
Hammer myHammer = Toolkit.factory(“Hammer”, Color.BLACK,50);
单纯从上面的代码上看,真的只有傻
b
才会选择静态工厂的方法,完全就是多此一举,直接
new
又快又爽,搞这么麻烦做莫斯(武汉话“什么”的意思)?
别急,别急,你急个莫
b
(武汉粗话:基本就是“你急个毛”的意思)?
下面就说说用静态工厂代替构造器的好处(
advantage
)和不好处(
disadvantage
)。
第一个好处,讲你都不信,行家们认为,构造器有一个不好的地方就是:这个方法的签名(
signture
)太固定了。
构造器的名字是固定的,生个
Hammer
,构造器的名字就是
Hammer
(……),唯一能变化的地方就是参数,假设我的这个锤子有两个很变态的构造需要:
1
:第一个参数是颜色(
Color
型),第二个参数是锤子头的重量(
int
型)。
Hammer
(
Color c, int kg
)
{
//remainder omited
}
2
:第一个参数是颜色(
Color
型),第二个参数是锤子的长度(
int
型)。
Hammer
(
Color c, int cm
)
{
//remainder omited
}
感觉满足需要了,但是细心一看,完了,构造器的参数列表类型重复了,肯定编译通不过,这是面向对象构造器天生的缺陷——唯一的变化就是参数,参数都分辨不了,就真的分辨不了。
而另外就算参数能分辨的了,构造器一多,它的参数一多,您根本就不知道每个参数是用来干什么的,只能去查阅文档,在您已经眼花缭乱的时候再去查文档,一个一个的对,折磨人的活。
这个时候,您就可以考虑用静态工厂方法来实例化对象了。因为静态工厂方法有一个最简单的特点就是:他有可以变化的方法名(构造器的名字变不了)。用名字的不同来代表不同的构造需要,这么简单的普通的特点在这里就是它相对于构造器的
advantage
。
如上面的锤子的例子可以这样:
1
:
Hammer.produceByWeight (Color c, int kg){
//remainder omited
}
2
:
Hammer.produceByHeight (Color c, int cm){
//remainder omited
}
这是不是一目了然多了。嗯,我是这样认为的。
第二个好处,“静态工厂方法不需要每次都真的去实例化一个对象”——其实这也是另一些优化方法的前提。
构造器的每次
invoke
必定会产生一个新的对象,而静态工厂方法经过一定的控制,完全可以不用每次
invoke
都生成一个新的对象。
为什么不每次都生成一个对象的原因就不必说了,因为原因太明显。这个原因就是为什么要“共享”对象的原因。
下面讲讲通常使用的两种共享具体策略,也就是具体方法了:
1
:单例模式的需要,一旦需要某个对象有单例的需要,必定对于这类对象的构造只能用静态工厂方法了。
2
:
flyweight
模式和不变(
immutable
)
模式的需要,这两个模式很多时候都说一起使用的,一旦一些对象我们认为是不变的,那自然就想拿来重用,也就说共享,而
flyweight
就是用来重用这些小粒度对象的。
如
Boolean.valueOf (boolean)
方法:
Boolean a = Boolean.valueOf (100);
Boolean b = Boolean.valueOf (100);
a, b两个引用都是指向同一个对象。
这些对象都是不变的,而
valueOf
的控制就是用的
flyweight
方法。
这种一个状态(如上面一个数字)对应的对象只有一个还有一个好处,就是可以直接通过比较“引用”来判断他们是否
equel
(这里的
equel
是逻辑相等的意思),以前需要
a.equels(b)
,而一旦用“
flyweight
模式和不变(
immutable
)
模式”后,避免了产生多余的相同对象,用
a==b
就可以达到
a.equels(b)
的目的了。这样当然优化了
performance
。
第三个好处,其实就是工厂方法的核心好处——我把它称为“抽象类型构造器”。它可以为我们提供一个抽象类型的实例,同时必要的隐藏了抽象类型的具体结构。这是
new
怎么都达不到的。
这种模式的好处其实就是面向对象的最核心的好处,抽象和具体可以分离,一旦抽象定义好了,具体的东西可以慢慢的变化,慢慢的拓展——开闭原则。
如
Collections Framework API
,都是描述集合类型的接口,也就是对于客户端来看,只有
Collection
这个类要认识,而实际上,实现这个接口的
Collection
是多种多样的。如果要让用户都知道这些具体实现的
Collection
,就增加了复杂度。
这时,通过一个静态工厂方法,就可以隐藏各种
Collection
的具体实现,而让
Client
只使用返回的
Collection
对象就可以了。
这里还可以加上一些权限控制,如这些实现只要对于工厂来讲是可以访问的,不用是
public
的,而他们只要通过
public
的工厂就可以提供给用户。非常有利于代码的安全。
静态工厂方法的第一个缺点就是:使用静态工厂方法创建的类的构造器经常都是非公共或非
protected
的。
这样,以后这些类就没有办法被继承了。不过也有人说,不用继承就用
composition
呗。也是!呵呵。
静态工厂方法的第二个缺点是:在
jdk
文档里,这些静态工厂方法很难跟别的静态方法相区别。
而文档中,构造器是很容易看到的。
为了一定程度解决这个问题,我们可以用一些比较特别的名字来给这类静态工厂方法来命名。最常用的有:
valueOf
——
用来放回跟参数“相同值”的对象。
getInstance
——
返回一个对象的实例。单例模式中,就是返回单例对象。
总结:静态工厂方法和构造器都有各自的特点。最好在考虑用构造器之前能先考虑一下静态工厂方法,往往,后者更有用一点。如果权衡了以后也看不出那个好用一些,那就用构造器,毕竟简单本分多了。