《Effictive Java 一》

一、关于对象的创建和销毁

Item1. 用静态工厂方法替代构建函数

好处: 静态工厂方法有名字,可以表示具体的含义。 静态工厂方法调用时可以不必要每次都创建新的对象。 可以返回更加抽像的对象,例如父类。例如一个传入名字参数,返回对象的子类,但返回结果可以统一成一个父类。

不足: 如果是private可能创建子类很麻烦。 另外也别的静态方法不是很好的区分开了,除非用这样的名字:valueOf(),getInstance,getType,newInstance, newType.

Item2. 如有多个构造参数,建议使用builder

好处:可以让多参数的构造更简单。例如:

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100)
.sodium(35).carbohydrate(27).build();

Item3. 一个只能单个元素的枚举类型是实现单例的最好方法。

之前我们写单例,都是私有构造+静态public方法,现在也可以这样: public enum Elvis { INSTANCE;public void leaveTheBuilding() { ... }}

Item4. 不想实例化的类最好加个私有的构造方法。

例如Math,Array,这样就不会被滥用了

Item5. 不要创建非必须的对象

例如:String s = new String("stringette"); // DON'T DO THIS! 可以这样 :String s = "stringette";

Item6. 知道什么时候显示的将引用标为null

好处:如果还用了程序就直接异常了而不是错误的执行。

二、对于所有对象都适用的方法

这些非变量方法例如:equals,hashCode, toString, clone, finalize

Item7. 知道什么时候override equals方法

写错了equals方法会导致很严重的后果,并且这些问题会不好查。所以当确定有以下的情况时,最好不要重写equals方法:

1. 每个实例本质上都是唯一的。例如Thread
2. 程序员不care是否需要在逻辑上对比实例。
3. 父类已重写,并且满足了子类的逻辑相等的需求了。
4. equals方法是私有的或者重来不会被调用的。例如一调就主动抛异常的情况,就不用重写了。

如果要写,需要确定以下几点:自反、对称、传递、一致、equals(null)==false。 总结起来,写好一个equals方法,注意以下几点:

1. 使用==来检查参数是否是一个这个对象的引用 
2. 使用instanceof()来检查参数是不是有正常的类型
3. 将参数转为正确的类型
4. 对于对象中的重要变量,检查是否一致
5. 写完后,问下自己:Is it symmetric(对称)? Is it transitive(可传递)? Is it consistent(一致性)? 最好别仅仅问,而是写一下单元测试。
例子:
    if (o == this) return true;
    if (!(o instanceof PhoneNumber))
    PhoneNumber pn = (PhoneNumber)o;
    return pn.lineNumber == lineNumber
        && pn.prefix == prefix
        && pn.areaCode == areaCode;

Item8. 重写了equals方法后一定要重写hashCode

否则会在hashbased容器中遇到问题。 例如上文中没有重写hashCode方法,下面的代码将得到null

Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
m.put(new PhoneNumber(707, 867, 5309), "Jenny");

那么如何写好一个hashCode()呢?

1. 找一些常量质数,例如17
2. 对于每一个重要的字段(变量),进行hash编码:
    boolean: f ? 1 :0
    byte,short,char,int: (int)f
    long: (int)(f ^ f>>>32))
    float: Float.floatToInt(f)
    double: double.doubleToLongBits(f),再用long来操作
    如果字段是一个引用,那需要用这个引用的hashCode,如果是空就返回0
    如果是数组,那把每一个元素当作一个独立的字段,如果数组中元素是唯一的,直接用Array.hashCode()
3. 对于上面的编码,返回result = 31 * result + c
4. 写完了之后再问一下自己,最后测试一下
例子:
    @Override public int hashCode() {
        int result = 17;
        result = 31 * result + areaCode;
        result = 31 * result + prefix;
        result = 31 * result + lineNumber;
        return result;
    }

如果对象是不变的,考虑不要每次都算,直接存在private volatile int hashCode;然后计算是看一下是否是0这个初始值。另外,不要因为开销问题而不写hashCode()

Item9. 务必重写toString()

否则异常了打印出来的东西看不懂。 记得最好输出时格式化一下。

Item10. 谨慎使用clone()方法,我自己就不用了

因为每次clone,都会先调super.clone(),有时我甚至不知道父类的clone对不对或者是否有实现 。 最好用一个拷贝构造器来完成clone.

Item11. 考虑一下实现 comparable接口

不是必须,但如果想在容器中排序,可以考虑一下。

三、类和接口

Item12. 最小化类和成员可访问性

对内部数据和实现的隐藏程度是衡量一个好的设计和不好的设计的重要因素之一。 开发原则就是:让尽可能多的类和方法隐藏起来。 几种可访问程度 :public,private ,protected(子类),还有一个叫package private(只有包内可访问) 一些注意点: 实例变量需要private,否则可能会遇到线程安全问题

Item13. 最小化可变性

我理解就是能不写set就不写set。不可变类更简单、更少的线程安全问题。

Item14. 相对于继承来说,更好的是用组合

有时候父类的方法已经帮子类做过了,就会导致了重复。例如hashSet的addAll方法。 一旦用了继承,就得说详细说明每一个覆盖的方法做了什么事,并且注意这些覆盖的方法可能在子类的构造前就被父类执行了。

Item15. 接口优于抽象类

接口不应该存放一些公用的常量。加入常量的一个好方法是专门写一个常量类。

Item16. 类层次要优先于标签

有的类为了区别不同的实例,在里面加入type这样的枚举类型,但这样不如果直接弄出抽象类,再写不同类型的子类。一般来说,都不要写标签。

Item17. 用函数对象表示策略

函数对象可以理解为像Comparator这样的只为了实现一个方法的对象。它没有字段,所以更适合写成单例

Item18. 优先考虑静态成员类

嵌套类有四种:静态成员类、非静态成员类、匿名类、局部类。注意每种的用法。如果内部类不需要引用外部类的实例,就写成静态成员类,否则写成非静态成员类。如果这个内部类在一个方法中使用,并且已知在别处有了这个类的说明了,就写成匿名类吧,如果别外也没有标识,就写成局部类。

继续阅读:《Effictive Java 阅读笔记二》

comments powered by Disqus