【转】effective java 笔记1
本来想自己写笔记,好在上网翻了一下,发现有人做得更好。借用一下了。
from:http://blog.csdn.net/ilibaba/archive/2009/01/10/3746123.aspx
前言: 清晰性和简介性是最为重要的
一个模块的使用者永远也不应该被模块的行为所迷惑(那样就不清晰了),模块要尽可能的小,但又不能太小【术语模块(module):是指任何可重用的软件组件,从单个方法到包含多个包的复杂系统都可以是一个模块】。代码应该被重用,而不是拷贝,模块之间的相依性应该尽可能降低到最小,错误应该尽早被检测出来,理想的情况下是在编译的时刻。
NO.1 考虑用静态工厂方法代替构造函数
静态工厂方法好处:
①构造函数有命名的限制,而静态方法有自己的名字,更加易于理解。
②静态工厂方法在每次调用的时候不要求创建一个新的对象。这种做法对于一个要频繁创建相同对象的程序来说,可以极大的提高性能。它使得一个类可以保证是一个singleton;他使非可变类可以保证“不会有两个相等的实例存在”。
③静态工厂方法在选择返回类型时有更大的灵活性。使用静态工厂方法,可以通过调用方法时使用不同的参数创建不同类的实例,还可以创建非公有类的对象,这就封装了类的实现细节。
静态工厂方法坏处:
①如果一个类是通过静态工厂方法来取得实例的,并且该类的构造函数都不是公有的或者保护的,那该类就不可能有子类(被继承),子类的构造函数需要首先调用父类的构造函数,因为父类的构造函数是private的,所以即使我们假设继承成功的话,那么子类也根本没有权限去调用父类的私有构造函数,所以是无法被继承的 。
②毕竟通过构造函数创建实例还是SUN公司所提倡的,静态工厂方法跟其他的静态方法区别不大,这样创建的实例谁又知道这个静态方法是创建实例呢?弥补的办法就是:静态工厂方法名字使用valueOf或者getInstance.
总结:静态工厂方法和公有的构造函数都有他们各自的用途,我们要理解他们各自的长处,避免一上来就用构造函数,通常静态工厂更加合适。如果没有其他因素强烈的影响我们的选择,最好还是简单的选择构造函数,毕竟他是语言提供的规范。
NO.2 试用私有构造函数强化singleton属性
实现singleton的方法有两种:(其实这两种区别不大)
方法一:公有的静态成员是一个final域,成员的声明很清楚的表达了这个类是一个singleton。
public class Elvis {
- public static final Elvis INSTANCE = new Elvis();
- private Elvis() { }
- public void leaveTheBuilding() {
- System.out.println("Who a baby, I'm outta here!");
- }
- // This code would normally appear outside the class!
- public static void main(String[] args) {
- Elvis elvis = Elvis.INSTANCE;
- elvis.leaveTheBuilding();
- }
- }
方法二:提供一个公有的静态方法,而不是公有的静态final域。该方式提供了更大的灵活性,在不改变API的前提下,可以把该类改成singleton或者非singleton的。
public class Elvis {
- private static final Elvis INSTANCE = new Elvis();
- private Elvis() { }
- public static Elvis getInstance() { return INSTANCE; }
- public void leaveTheBuilding() {
- System.out.println("Who a baby, I'm outta here!");
- }
- // This code would normally appear outside the class!
- public static void main(String[] args) {
- Elvis elvis = Elvis.getInstance();
- elvis.leaveTheBuilding();
- }
- }
一般来说,第一种方法效率稍微高一些,然后,采用第一种方法实现singleton后,就没有改变的余地了,当你想把该类改成非singleton,显然是不行的了。如果确定该类是一个singleton,用第一个方法是有意义的,想保留余地,用第二种。用第2种方法的时候,假如该类实现了serializable接口,那应该重写(override)readResolve()方法,否则再反序列化的时候是会产生一个新的实例,这与singleton相违背了!
NO.3 通过私有的构造函数强化不可实例化的能力
在面向对象程序设计中,假如存在太多只有静态属性和静态方法的类;那么,面向对象的思想可能在这会损失殆尽。但是,并不能说面向对象的程序中就不应该出现只有静态属性和静态方法的类,相反,有时候我们还必须写这样的类作为工具类。这样的类怎么实现呢?有人可能会把该类定义成抽象类(Abstract class),的确,抽象类是不可以实例化的,但是别忘了还有继承,继承了抽象类的子类在实例化时候,默认是会先调用父类无参数的构造函数的(super();),这时候,父类不是也被实例化了嘛?其实我们可以这样做,把该类的构造函数定义为私有的(private),而类的内部又不调用该构造函数的话,就成功了。这样带来的后果就是该类成了 final的,不可能再被任何类继承了,要被继承,得提供一个公有(public)的或者保护(protect)的构造函数,这样才能被子类调用。
NO.4 避免创建重复的对象
如果一个对象是非可变(immutable)的,那么他总是可以被重用的,如:
- String s1 = new String("test"); //不推荐,"test"本来就是一个String实例,如果此方法在一个循环中或者被频繁的调用,将会严重影响性能
- String s2= "test"; //推荐方式
注:s1==s2 //false ; s1.equals(s2) //true
对于提供静态方法和构造函数的非可变类,推荐使用静态方法,这样可以避免重复创建对象,如:Boolean.vauleOf(String)方法优于构造函数Boolean(String)
如下Person类在每次调用isBabyBoomer()方法时都需新创建对象,极大的影响了性能
- import java.util.*;
- public class Person {
- private final Date birthDate;
- public Person(Date birthDate) {
- // Defensive copy - see Item 39
- this.birthDate = new Date(birthDate.getTime());
- }
- // Other fields, methods omitted
- // DON'T DO THIS!
- public boolean isBabyBoomer() {
- // Unnecessary allocation of expensive object
- Calendar gmtCal =
- Calendar.getInstance(TimeZone.getTimeZone("GMT"));
- gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
- Date boomStart = gmtCal.getTime();
- gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
- Date boomEnd = gmtCal.getTime();
- return birthDate.compareTo(boomStart) >= 0 &&
- birthDate.compareTo(boomEnd) < 0;
- }
- }
改进方案如下:
- import java.util.*;
- class Person {
- private final Date birthDate;
- public Person(Date birthDate) {
- // Defensive copy - see Item 39
- this.birthDate = new Date(birthDate.getTime());
- }
- // Other fields, methods
- /**
- * The starting and ending dates of the baby boom.
- */
- private static final Date BOOM_START;
- private static final Date BOOM_END;
- static {
- Calendar gmtCal =
- Calendar.getInstance(TimeZone.getTimeZone("GMT"));
- gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
- BOOM_START = gmtCal.getTime();
- gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
- BOOM_END = gmtCal.getTime();
- }
- public boolean isBabyBoomer() {
- return birthDate.compareTo(BOOM_START) >= 0 &&
- birthDate.compareTo(BOOM_END) < 0;
- }
- }
这样类变量在类在初始化创建Calendar、Date、TimeZone时只需创建一次,已经初始化的不再进行初始化操作,性能好于前一种方法一百倍。
类初始化的顺序:先初始化父类的静态代码--->初始化子类的静态代码-->初始化父类的非静态代码--->初始化父类构造函数--->初始化子类非静态代码--->初始化子类构造函数。
NO.5 消除过期的对象引用
垃圾回收器不会对“过期引用”(def:永远不会再被解除的引用)的对象进行回收。如:数组中的元素先增加再减少这种情况,下标大于size()的那一部分就是过期引用的对象。
解决方法:
- public Object pop(){
- if(size == 0){
- throw new EmptyStackException();
- }
- Object result = elements[--size];
- elements[size] = null; //自减后把原来的引用置为null
- return result;
- }
优点:1、避免内存泄漏而造成的系统崩溃(内存泄漏也常见于缓存,由于缓存没有及时清除无用的条目而出现,可以使用weakHashMap来避免这种情况,参考:利用WeakHashMap避免因缓存条目过期而造成的内存泄漏问题http://blog.csdn.net/ilibaba/archive/2009/01/11/3754492.aspx);
2、如果他们后来又被错误地解除引用,程序能在第一时间抛出空指针异常;
NO.6 避免使用终结函数
终结函数(finalizer)可以用来回收不可到达的对象,就是说对象的生命周期结束后,可以用终结函数来回收为该对象分配的资源。
但是,终结函数执行线程的优先级很低,以至于我们不敢把对时间要求比较高的对象回收让终结函数来回收。JVM总是会延迟终结函数的执行。
对于急需回收对象,可以使用try-finally+显式的终止方法,在finally写回收对象的代码,这样就保证对象能及时被回收。例子,InputStream中的close方法
终结函数其实也是有用的。第一种情况是当作安全网,当忘了对对象显示回收的时候,用终结函数作为最后的安全屏障。第二种情况是:普通对象通过一个本地方法 委托给本地对象叫本地对等体。本地对等体不是普通对象,所以当委托给他的对象被回收的时候本地对等体并不会被回收,所以这时候终结函数就派上用场了。
最后要注意的是,当子类改写覆盖了超类的终结函数时候,如果不显式调用超类的终结函数,那么超类的终结函数将不会被执行。所以要记得执行super.finalizer(). 代码示例:
//Manual finalizer chaining
protected void finalize throws Throwable()
{
try{
//Finalize subclass state
}
finally{super.finalize();
}
}
为了防范子类忘了显式调用超类终结函数,可以使用终结函数守卫者。把终结函数放在一个匿名的类中,该类的唯一用途是终结其外围实例,外围类的每一个实例都会创建这样一个守卫者。代码:
public class Foo{
//Sole purpose of this object is to finalize outer Foo object
private final Object finalizerGuardian=new Object(){ //匿名类
//Finalize outer Foo object
}
};
}
Foo类并没有终结函数(除了从Object中继承的那个无关紧要的),所以子类的终结函数是否调用super.finalize并不重要。对于每一个带有终结函数,可以被继承的公有类来说,都应该考虑这样做。
总结:尽量不使用终结函数,除非作为安全网或者是用来回收不关键的本地资源。