第1条:考虑用静态工厂方法代替构造器
提纲:四大优势、两大缺点、一个切忌
1、四大优势
a、静态工厂方法与构造器不同的第一大优势在于,它们有名称,通过名称表达出方法的意思,使得用户使用起来更加清楚。
b、静态工厂方法与构造器不同的第二大优势在于,不必在每次调用它们的时候都创建一个新的对象。这使得不可变类可以使用预先构建好的实例,或者将构建好的实例缓存起来,进行重复利用,从而避免创建不必要的重复对象。
c、静态工厂方法与构造器不同的第三大优势在于,它们可以返回原返回类型的任何子类型的对象。这样我们在选择返回对象的类时就有了更大的灵活性。
d、静态工厂方法的第四大优势在于,在创建参数化类型实例的时候,它们使代码变得更加简洁。
例如:
public static <K, V> HashMap<K, V> newInstance(){
return new HashMap<K, V>();
}
提供如此的静态工厂方法,就可以如此调用了Map<String, List<String>> m = HashMap.newInstance();但是截止1.6版本还暂时无法这么做。不必我们常规的调用方式:
Map<String, List<String>> m = new HashMap<String, List<String>>();//两次指明参数化
2、两大缺点
a、静态工厂方法的主要缺点在于,类如果不含公有的或者受保护的构造器,就不能被子类化。
b、静态工厂方法的第二个缺点在于,它们与其他的静态方法实际上没有任何区别。
3、一个切忌
切忌第一反应就是提供公有的构造器,而不先考虑静态工厂方法
第2条:遇到多个构造器参数时要考虑用构建器
提纲:三种模式,一点建议
1、三种模式
a、重叠构造器模式
如何使用:你提供第一个只有必要参数的构造器,第二 个构造器有一个可选参数,依此类推,最后一个构造器包含所有可选参数,而且在类推过程中,每个构造器都会调用this(参数1,参数2,..参数n)来调用下一个构造器,并在调用时,将下个构造器需要赋的值但客户端又没有给的可选参数赋上初始值,最后在最后一个构造器完成对域的整个赋值操作
不足之处:重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然较难以阅读
b、JavaBeans模式
如何使用:调用一个无参构造器来创建对象,然后调用setter方法来设置每个必要的参数,以及每个相关的可选参数
严重缺点:在构造过程中JavaBean可能牌不一致的状态,类无法仅仅通过检验构造器参数的有效性来保证一致性。
不足之处:JavaBeans模式阻止了把类做成不可变的可能,这就需要程序员付出额外的努力来确保它的线程安全。
c、Builder模式
如何使用:不直接生成想要的对象 ,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builder对象。然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后,客户端调用无参的build方法来生成不可变的对象。这个builder是它构建的类的静态成员类。
优点:既能保证像重叠构造器模式那样的安全性,也能保证像JavaBeans模式那么好的可读性。Builder模式十分灵活,可以利用单个builder构建多个对象。builder的参数可以在创建对象期间进行调整,也可以随着不同的对象而改变。
不足:为了创建对象,必须先创建它的构建器。虽然创建构建器的开销在实践中可能不那么明显,但是在某些十分注重性能的情况下,可能就成问题。Builder模式还比重叠构造器模式更加冗长,因此它只在有很多参数的时候才使用,比如4个或者更多个参数。
2、一 点建议
通常一开始就使用构建器,如果类的构造器或者表态工厂中具有多个参数,设计这种类时,Builder模式就是种不错的选择,特别是当大多数参数都是可选的时候。与使用传统的重叠构造器模式相比,使用Builder模式的客户端代码更易于阅读和编写,构建器也比JavaBeans更加安全
第3条:用私有构造器或者枚举类型强化Singleton属性
提纲:三种方式,一个先见
1、三种方式
在Java1.5发行版本之前,实现Singleton有两种方式
a、把构造器保持为私有的,并导出公有的静态成员,以便允许客户端能访问该类的唯一实例。
b、把构造器保持为私有的,并保持私有的静态成员,利用公有的成员静态工厂方法返回这个私有的静态成员
为了使利用这其中一种方式实现的Singleton类变成是可序列化的,必须在Singleton类中加入下面这个readResolve方法:
//readResolve method to preserve singleton property保护Singleton属性
private Object readResolve(){
return INSTANCE;//静态成员-类对象
}
c、从Java1.5发行版本起,实现Singleton还有第三种方式,只需编写一个包含单个元素的枚举类型:
public enum Elvis {
INSTANCE;
public void leaveTheBuilding(){}
}
2、一个先见
枚举方式在功能上与公有域方法相近,但是它更加简洁,无偿地提供了序列化机制,绝对防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法
第4条:通过私有构造器强化不可实例化的能力
提纲:一种方式、一个副作用
1、一种方式
为类提供私有构造器
public class UtilityClass{
private UtilityClass(){
throw new AssertionError();
}
}
2、一个副作用
它使得一个类不能被子类化。所有的构造器都必须显示或隐式地调用超类构造器,在这种情形下,子类就没有可访问的超类构造器可调用了。
第5条:避免创建不必要的对象
提纲:三种情景、三个总结
1、三种情景
a、重用不可变的对象:String类型
反面例子:String s = new String(“stringette”);如果这种用法是在一个循环中,或者是在一个被频繁调用的方法中,就会创建成千上万不必要的String实例
正面教材:String s = “stringette”;对于所有在同一台虚拟机中运行的代码,只要它们包含相同的字符串字面常量,该对象就会被重用。
b、重用不会被修改的对象
如果某个方法中,有对象的创建,但创建之后,它就注定不会被修改,这个时候可以将这些对象提取出来作为final静态域,并使用静态代码块(静态的初始化器)将其进行初始化操作
c、自动装箱,会创建多余对象的新方法
反面例子:
Long sum = 0L;
for(long i = 0; i < Integer.MAX_VALUE; i++{
sum +=i;
}
变量sum被声明成Long而不是long,意味着程序构造了大约2^31个多余的Long实例。将sum的声明从Long改成long,就不造成这样不必要的创建。要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱。
2、三个总结
a、不要错误地认为本条目所介绍的内容暗示着“创建对象的代价非常昂贵,我们应该要尽可能地避免创建对象”。相反,通过创建附加的对象,提升程序的清晰性,简洁性和功能性,这通常是件好事。
b、通过维护自己的对象池来避免创建对象并不是一种好的做法,除非池中的对象是非常重量级的。真正正确使用对象池的典型对象示例就是数据库连接池。建立数据库连接的代价是非常昂贵的,因此重用这些对象非常有意义。
c、在提倡使用保护性拷贝的时候,因重用对象而付出的代价要远远大于因创建重复对象而付出的代价。必要时如果没能实施保护性拷贝,将会导致潜在的错误和安全漏洞;而不必要地创建对象只会影响程序的风格和性能
第6条:消除过期的对象引用
提纲:内存泄漏的三种情形、发现与阻止
1、内存泄漏的三种情形
a、无意识的对象保持-不再用的对象还依然存在
解决方案:需要及时的消除过期的对象引用,将不需要的对象引用赋值为null
b、缓存-将对象引用放到缓存中,然后被遗忘掉了
解决方案:
1、WeakHashMap解决当所要的缓存项的生命周期是由该键的外部引用而不是由值决定时。
2、由一个后台线程(可能是Timer或者ScheduledThreadPoolExecutor)来完成清除掉缓存中没用的项
3、LinkedHashMap利用它的removeEldestEntry方法可以很容易在给缓存添加新条目的时候顺便进行旧条目的清理
4、对于更加复杂的缓存,必须直接使用java.lang.ref
c、监听器和其他回调-实现一个API,客户端在这个API注册回调,却没有显示地取消注册
解决方案:确保回调立即被当作垃圾回收的最佳方法是只保存它们的弱引用,例如,只将它们保存成WeakHashMap中的键
2、发现与阻止
通过仔细检查代码,或者借助于Heap剖析工具(Heap Profiler)才能发两点内存泄漏问题。
如果能够在内存泄漏发生之前就知道如何预测此类问题,并阻止它们发生,那是最好不过的了。
第7条:避免使用终结方法
提纲:两个缺点、两种用途、三种方式、四条建议
1、两个缺点
a、Java语言规范不仅不保证终结方法会被及时地执行,而且根本就不保证它们会被执行。
b、使用终结方法有一个非常严重的(Severe)性能损失。创建和销毁一个简单对象的时间大约为5.6ns,但增加一个终结方法使时间增加到了2400ns
2、两种用途
a、当对象的所有所有者忘记调用显示终止方法时,终结方法可以充当“安全网”
b、与对象的本地对等体有关。本地对等体是一个本地对象,普通对象通过本地方法委托给一个本地对象。终止方法应该完成所有必要的工作以便释放关键的资源。终止方法可以是本地方法,或者也可以调用本地方法。
3、三种方式
a、提供显示的终止方法(此时不用编写终结方法)
显示的终止方法通常与try-finally结构结合起来使用,以确保及时终止 。
Foo foo = new Foo(…);
try{
//Do what must be done with foo
}finally{
foo.terminate();//Explicit termination method显示终止方法
}
b、终结方法链-并不会自动执行
如果类(不是Object)有终结方法,并且子类覆盖了终结方法,子类的终结方法就必须手工调用超类的终结方法。
@Override
protected void finalize()throws Throwable{
try{
//Finalize subclass state
}finally{
super.finalize();
}
}
注意:如果子类实现者覆盖了超类的终结方法,但是忘了手工调用超类的终结方法(或者有意选择不调用超类的终结方法),那么超类的终结方法将永远也不会被调用
3、提供附加的对象,将终结方法放在一个匿名的类中(为了防止方式2的注意事项发生)
该匿名类的单个实例被称为终结方法守卫者finalizerGuardian,外围类的每个实例都会创建这样的一个守护者。外围实例在它的私有实例域中保存着一个对其终结方法守卫者的唯一引用,因此终结方法守卫者与外围实例可以同时启动终结过程 。
public class Foo{
private final Object finalizerGuardian = new Object(){
@Override
protected void finalize()throws Throwable{
…//Finalize outer Foo object
}
};
}
4、四条建议
a、除非是作为安全网,或者是为了终止非关键的本地资源,否则请不要使用终结方法
b、既然使用了终结方法,就要记住调用super.finalize
c、如果用终结方法作为安全网,要记得记录方法的非法用法。
d、如果需要把终结方法与公有的非final类关联起来,请考虑使用终结方法守卫者,以确保即使子类的终结方法未能调用super.finalize,该终结方法也会被执行
如果有疑问或对本博文或建议或看法或问题,欢迎评论、恳请指正!