Effective Java

本文介绍了Java编程中的40余项最佳实践,包括使用静态工厂方法、实现Singleton模式、避免重复创建对象、正确重写equals方法等,帮助开发者提高代码质量和效率。

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需要 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值