`

Effective Java

阅读更多

1. 考虑用静态工厂方法代替构造函数
    静态工厂方法优点:具有名字;每次调用时,不要求必须创建新对象;可以返回对象。
    实质就是静态方法,可以方便的调用。缺点是不能被实例化。

//定义静态方法   
Public class StaticTest {   
    public static String getResults(String name) {   
        return name;   
    }   
}   
  
//调用   
String name = StaticTest.getResults("tingor"); 

   

    服务提供框架: 有一个Properties文件,每个Key对应一个Class,框架用来注册一个类,然后实例化。

public abstract class Foo {
    private static Map implementations = null;
    private static synchronized void initMapIfNecessary() {
        if (implementations == null)
            implementations = new HashMap();
    }
    public static Foo getInstance(String key) {
        initMapIfNecessary();
        Class c = (Class) implementations.get(key);
        if (c == null)
            return new DefaultFoo();
        try {
            return (Foo) c.newInstance();
        } catch (Exception e) {
            return new DefaultFoo();
        }
    }
    public static void main(String[] args) {
        System.out.println(getInstance("NonexistFoo"));
    }
}

public class DefaultFoo extends Foo {}

  

 

2. 使用私有构造函数强化Singleton属性

    singleton只能被实例化一次,通常用来代表唯一性的系统组件。

方法一: 饥饿加载 - 性能稍微优先
public class Singleton() {
    public static final MySingleton instance = new MySingleton();
    private Singleton() {}
    public static MySingleton getInstance() {
        return INSTANCE;
    }
}

方法二: 懒加载
public class Singleton {   
    private static Singleton instance = null;   
    private Singleton() {}   
    public synchronized static Singleton getInstance() {   
        if (instance == null) {   
            instance = new Singleton();   
        }   
        return instance;   
    }   
}  

为了维护singleton性,在序列化的时候需要提供一个readResolve方法,以便对象在反序列化的时候,会创建一个新的实例
public class Singleton() {
    public static final Singleton instance = new MySingleton();
    private MySingleton() {}
    private Object readResolve() throws ObjectStreamException {
        return INSTANCE;
    }
}

 

 

3. 通过私有构造函数强化不可实例化的能力

    不能实例化一个类,一般用于工具类的范围。 

public class UtilityClass {
    private UtilityClass() {}
}

 

 

4. 避免创建重复的对象

    4.1 不要重复创建同一个对象 

String s = new String("silly");  //Don't do this
String s = "silly";  //should do this

 

    4.2 把只需要创建一次的局部变量变成final静态域

//错误做法(每次isBabyBoomer被调用的时候,都会创建Calendar和TimeZone,以及两个Date实例)

public class Person {
    private final Date birthDate;
    public Person(Date birthDate) {
        this.birthDate = birthDate; 
    }
    public boolean isBabyBoomer() {
        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;
    }
}

//改进之后
public class Person {
    private final Date birthDate;
    public Person(Date birthDate) {
        this.birthDate = birthDate; 
    }
    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);
        Date boomStart = gmtCal.getTime();
        gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
        Date boomEnd = gmtCal.getTime();
    }
    public boolean isBabyBoomer() {
        return birthDate.compareTo(boomStart) >=0 && birthDate.compareTo(boomEnd) < 0;
    }
}

 

 

5. 消除过期的对象引用

    如果对象一直不被回收,那么很容易引起OutOfMemoryError的错误。

//Stack中,先增长,然后收缩,那么从栈弹出的对象将不会被回收;另一个例子在缓存,解决的办法是使用WeakHashMap
public class Stack {
    private Object[] elements;
    private int size = 0;
    public Stack(int initialCapacity) {
        this.elements = new Object[initialCapacity];
    }
    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }
    public Object pop() {
        if (size == 0) throw new EmptyStackException();
        return elements[--size];
    }
    private void ensureCapacity() {
        if (elements.length == size) {
            Object[] oldElements = elements;
            elements = new Object[2 * elements.length + 1];
            System.arraycopy(oldElements, 0, elements, 0, size);
        }
    }
}

//pop方法修改版本
public Object pop() {
    if (size == 0) throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] = null;  //消除旧的引用
    return result;
}

 

 

6. 避免使用终结函数

    6.1 终结函数并不能保证能够执行,如果非要使用,那么首要考虑显示终止方法

Foo foo = new Foo(...);
try {
    //Do what mush be done with foo
} finally {
    foo.terminate();  //foo自带的外在终止方法,还有例如数据库的关闭连接方法
}

 

    6.2 如果一类有一个终结函数,并且一个子类改写了终结函数,那么子类的终结函数必须要调用父类的终结函数

protected void finalize() throws Throwable {
    try {
        //Finalize subclass state
    } finally {
        super.finalize();
    }
}

 

    6.3 如果一个子类改写了超类的终结函数,但忘了手工调用超类的终结函数,那么使用终结函数守卫者(finalizer guardian)来实现其终结,注意该终结函数放在一个匿名的类中 

public class Foo {
    private final Object finalizerGuardian = new Object() {
        protected void finalize() throws Throwable {
            //finalize outer Foo object
        }
    }
}

 

 

7. 在改写equals的时候请遵守通用约定
    当一个类有自己特有的逻辑相等原则,而超类也没有改写,则需要改写Object.equals
    自反性:x.equals(x)一定为true
    对称性:y.equals(x)返回true, x.equals(y)一定也为true
    传递性:x.equals(y)为true,y.equals(z)为true,那么x.equals(z)一定为true
    一致性:多次调用x.equals(y)要么一直返回true,要么一致返回false
    非空性:非空x,x.equals(null)一定返回false

public boolean equals(Obejct otherObject) {
    if (this == otherObject) return true; //检测this与otherObject是否引用同一个对象
    if (otherObject == null) return false; //检测otherObject是否为null
    if (getClass() != otherObject.getClass)   
        return false; //比较this与otherObject是否属于同一个类 
    if (!otherObject instanceof thisClass)
        return false; //该方法可以替代上一行判断   
    Employee other = (Employee)otherObject; //将otherObject转换为相应的类型 
    return name.equals(other.name) && salary == other.salary; //对所有域进行比较
}

  

 

8. 改写equals时总是要改写hashCode

private volatile int hashCode = 0;  //延迟初始化
public int hashCode() {
    if (hashCode == 0) {
        int result = 17;
        result = 37 * result + areaCode;
    }
    return hashCode;
}

 

 

9. 总是要改写toString
    主要用于输出的程序便于调试

public String toString() {
    return getClass().getName() + "[name=" + name + ",salary=" + 
        salary + ",hireDay=" + hireDay + "]";
} 

 

 

10. 谨慎地改写clone

    对于该条目,我认为最好不使用clone方法。

 

 

11. 考虑实现Comparable接口

    compareTo方法允许简单的相等比较,也允许执行顺序比较,一般用于Arrays等数据结构

    Arrays.sort(a);

 

 

12. 使类和成员的可访问能力最小化

    好的设计,应该隐藏内部数据和具体细节

 

 

13. 支持非可变性

    例如: String,BigInteger都是非可变类

    非可变类,实例不能被修改,遵循5条规则
    不要提供修改对象的方法
    保证没有子类改写的方法
    使所有的域都是final
    使所有的域都成为私有的
    保证对于可变组件的互斥访问

    非可变对象本质上是安全的,所以不要求同步,这样类定义为final,就可以被多个线程共享

 

 

14. 复合优先于继承

    包装类即装饰器的使用

 

 

15. 要么专门为继承而设计,并给出文档说明,要么禁止继承

 

 

16. 接口优于接口类

 

 

17. 接口只是被用于定义类型

    在接口中,最好不要定义常量

 

 

18. 优先考虑静态成员类

    声明的成员类不要求访问外围实例,那么应该加入static修饰符

    嵌套类: 静态成员类,非静态成员类,匿名类,局部类

 

 

19. 用类代替结构

 

 

20. 用类层次代替联合

 

 

21. 用类代替enum结构

    在Java5中,又加入了enum这种结构

//类型安全枚举
public class Suit {
    private final String name;
    private Suit(String name) {
        this.name = name;
    }
    public String toString() {
        return name;
    }
    public static final Suit CLUBS = new Suit("clubs");
    public static final Suit DIAMONDS = new Suit("diamonds");
    public static final Suit HEARTS = new Suit("hearts");
    public static final Suit SPADES = new Suit("spades");
}

 

 

22. 用类和接口来代替函数指针

 

 

23. 检查参数的有效性

    应该在抛出空指针或者其他不可获知异常之前,进行参数检查

public BigInteger mod(BigInteger m) {
    if (m.signum() <= 0) 
        throw new ArithmenticException("Modulus not positive");
}

 

 

24. 需要时使用保护性拷贝

//原始类
public final class Period {
    private final Date start;
    private final Date end;
    public Period(Date start, Date end) {
        if (start.compartTo(end) > 0)
            throw new IllegalArgumentException(start + " after " + end);
        this.start = start;
        this.end = end;
    }
    public Date start() {
        return start;
    }
    public Date end() {
        return end;
    }
}

//修改Date
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); //已经修改了时间

//重构之后的类
public Period(Date start, Date end) {
    this.start = new Date(start.getTime());
    this.end = new Date(end.getTime());
    if (start.compartTo(end) > 0)
        throw new IllegalArgumentException(start + " after " + end);
}
public Date start() {
    return (Date)start.clone();
}
public Date end() {
    return (Date)end.clone();
}

 

 

25. 谨慎设计方法的原型

    谨慎选择方法的名字
    不要过于追求提供便利的方法
    避免长长的参数列表(一般为3个): 有两个方法来避免:把一个方法分解成多个方法;创建辅助类
    对于参数类型,优先使用接口而不是类
    谨慎使用函数对象

 

 

26. 谨慎使用重载

 

 

27. 返回零长度数组而不是null

 

 

28. 为所有导出的API元素编写文档注释

 

 

29. 将局部变量的作用域最小化
    局部变量应该在第一次使用它的地方声明,而不要过早在前面声明
    如果循环终止后循环变量不再需要,那么for循环应该优先于while循

 

 

30. 了解和使用库
    java.lang,java.util,java.io

 

 

31. 如果要求精确的答案,请避免使用float和double
    尤为不合适货币计算,可以考虑BigDecimal

 

 

32. 如果其他类型更合适,则尽量避免使用字符串

 

 

33. 了解字符串连接的性能
    使用StringBuffer或者StringBuilder代替

 

 

34. 通过接口引用对象
    List subscribers = new ArrayList();
    如果没有接口,那么就用基类来引用

 

 

35. 接口优于反射机制

 

 

36. 谨慎地使用本地方法

 

 

37. 谨慎地进行优化
    努力避免那些限制性能的设计决定,考虑你的API设计决定的性能后果

 

 

38. 遵守普遍接受的命名惯例

 

 

39. 只针对不正常的条件才使用异常

 

40. 对于可恢复的条件使用被检查的异常,对于程序错误使用运行时异常

 

 

41. 避免不必要地使用被检查的异常

 

 

42. 尽量使用标准的异常
    IllegalArgumentException: 参数的值不合适
    IllegalStateException: 对于这个方法调用而言,对象状态不合适
    NullPointerException: 在null被禁止的情况下参数值为null
    IndexOutOfBoundException: 下标越界
    ConcurrentModificationException: 在禁止并发修改的情况下,对象检测到并发修改
    UnsupportOerationException: 对象不支持客户请求的方法

 

 

43. 抛出的异常要适合于相应的抽象
    可以把异常进行包装,转译成容易理解的异常

 

 

44. 每个方法抛出的异常都要有文档

 

 

45. 在细节消息中包含失败-捕获异常
    可以在异常中加入失败的消息,关键字符

 

 

46. 努力使失败保持原子性

//在其他动作之前,就进行size检查
public Object pop() {   
    if (size == 0) 
        throw new EmptyStackException();   
    Object result = elements[--size];   
    elements[size] = null;  //消除旧的引用   
    return result;   
}  

 

 

47. 不要忽略异常

 

 

48. 对共享可变数据的同步访问

 

 

49. 避免过多的同步

 

 

50. 永远不要在循环的外面调用wait
    一般使用notifyAll()

 

 

51. 不要依赖于线程调度器

 

 

52. 线程安全性的文档化

 

 

53. 避免使用线程组

 

 

54. 谨慎地实现Serializable
    实现了Serializable的代价是,一旦一个类发布,改变这个类的实现的灵活性将降低
    为了继承而设计的类和内部类应该很少实现Serializable

 

 

55. 考虑使用自定义的序列化形式

 

 

56. 保护性地编写readObject方法

 

 

57. 必要时提供一个readResolve
    对于Singleton需要 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics