《effective java》之二:对于所有对象都通用的方法 博客分类: Java effectivejava
第8条:覆盖equals时请遵守通用约定:
以下四种情况,默认实现正是所期望的结果:
* 类的每个实例本质上是唯一的。
* 不关心类是否提供了逻辑相等的测试功能
* 超类已经覆盖了equals方法,继承过来的子类同样适用的
* 类是私有的或者包级私有的,可以确定它的equals方法永远不会被调用,那么请覆盖这个equals方法:
@Override public boolean equals(Object o) { throw new AssertionError(); // Method is never called }
什么时候我们应该正常覆盖equals方法呢?
如果类具有自己特有的逻辑相等概念,而且超类还没有覆盖equals以实现期望的行为。这通常属于值类的情形。
JavaSE6规范中定义了equals的约定,必须实现等价关系 equivalence relation:
* 自反性 reflexive。对于任何非null引用x,x.equals(x)返回true
* 对称性 symmetric。对于任何非null x和y ,。。。
* 传递性 transtive。对于任何非null x、y、z。。。
* 一致性 consistent。对于任何非null x和y,对此调用x.equals(y),返回结果一样
* 对于任何非null x,x.equals(null)返回false
实现高质量equals方法的诀窍:
* 使用==操作符检查参数是否为这个对象的引用,如果是,返回true
* 使用instanceof 操作符检查参数是否为正确的类型,如果不是,返回false
* 把参数转换成正确的类型,之前用instanceof进行了测试
* 对于该类中的每个关键significant域,检查参数中域是否与该对象中对应匹配,全部测试成功后,返回true,否则返回false。
对于既不是flast,也不是double类型的基本数据类型域,可以使用==操作符比较,对于对象引用域,可以递归地使用equals,对于float域,可以使用Float.compare方法,对于double域,则使用Double.compare。对于数组的话,可以使用Arrays.equals方法。
对于可以包含null的域,下面的习惯用法:
(field==null ? o.field==null : field.equals(o.field))
最后,还有一些忠告:
-- 覆盖equals时总要覆盖hashCode
-- 不要企图让equals方法过于智能
-- 不要把equals方法的Object 参数改成其他的,不然就不是覆盖equals方法了,而只是重载。
-- 添加@Override注解
第9条:覆盖equals时总要覆盖hashCode方法:
1. 把某个非0的常数值比如17,保存在一个名为result的int类型变量中。
2. 对于对象中每个关键域f(指equals方法中涉及的域),完成以下步骤:
a. 为该域计算int类型散列码c:
①. 如果该域是boolean类型,则计算(f ? 1 : 0)
②. 如果该域是byte、char、short、int类型,则计算(int)f
③. 如果该域是long类型,计算(int) (f^(f>>>32))
④. 如果该域是float类型,计算Float.floatToIntBits(f)
⑤. 如果该域是double类型,计算Double.doubleToLongBits(f),返回按照步骤2.a.③为得到的long类型计算散列值。
⑥. 如果该域是一个对象引用,并且该类的equals方法通过递归调用equals方式比较这个域,则同样为这个域递归调用hashCode。如果这个域值为null,则返回0
⑦. 如果该域是数组,对于每个元素,递归调用上述规则,然后根据2.b做法将这些散列值组合起来。如果数组中每个元素都很重要,可以利用Arrays.hashCode方法。
b. 按照下面的公式,把步骤2.a中计算得到的散列值c合并到result中:
result = 31*result + c;
3. 返回result
4. 写完后不要忘记用单元测试来验证下
一个小例子:
public final class PhoneNumber { private final short areaCode; private final short prefix; private final short lineNumber; public PhoneNumber(int areaCode, int prefix, int lineNumber) { rangeCheck(areaCode, 999, "area code"); rangeCheck(prefix, 999, "prefix"); rangeCheck(lineNumber, 9999, "line number"); this.areaCode = (short) areaCode; this.prefix = (short) prefix; this.lineNumber = (short) lineNumber; } private static void rangeCheck(int arg, int max, String name) { if (arg < 0 || arg > max) throw new IllegalArgumentException(name + ": " + arg); } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof PhoneNumber)) return false; PhoneNumber pn = (PhoneNumber) o; return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode; } // Broken - no hashCode method! @Override public int hashCode() { int result = 17; result = 31 * result + areaCode; result = 31 * result + prefix; result = 31 * result + lineNumber; return result; } // Lazily initialized, cached hashCode - Page 49 // private volatile int hashCode; // (See Item 71) // // @Override public int hashCode() { // int result = hashCode; // if (result == 0) { // result = 17; // result = 31 * result + areaCode; // result = 31 * result + prefix; // result = 31 * result + lineNumber; // hashCode = result; // } // return result; // } public static void main(String[] args) { Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>(); m.put(new PhoneNumber(707, 867, 5309), "Jenny"); System.out.println(m.get(new PhoneNumber(707, 867, 5309))); } }
第10条:始终要覆盖toString:
这个没啥好讲的
第11条:谨慎的覆盖clone:
从jdk1.5开始,引入了协变返回类型 covariant return type作为泛型,换句话说,就是目前覆盖方法的返回类型可以是被覆盖方法的返回类型的子类了。
所有实现了Cloneable接口的类应该用一个公有方法覆盖clone。此公有方法先调用super.clone,然后修正任何需要修正的域。
最佳实践是提供一个类似于拷贝构造器的静态工厂:
public static Yum newInstance(Yum yum);
由于clone方法有许多的缺点,专家级程序员干脆不去覆盖clone方法,也从来不去调用它,除非拷贝数组。
第12条:考虑实现Comparable接口:
比较基本整数类型时候使用> <等,浮点域使用Double.compare()或者Float.compare()
本人博客已搬家,新地址为:http://yidao620c.github.io/