参考:《Effective Java》、《Thinking in java》http://www.oschina.net/question/82993_75533 http://zhangjunhd.blog.51cto.com/113473/71571/ 《改善java程序的151个建议》
1.何时需要重写equals() 当一个类有自己特有的“逻辑相等”概念(不同于对象身份的概念)。
2.设计equals() [0]用==运算符判断是否为空,且是否指向同一个对象。 [1]使用instanceof操作符检查“实参是否为正确的类型”。 [2]对于类中的每一个“关键域” ,检查实参中的域与当前对象中对应的域值。 [2.1]对于非float和double类型的原语类型域,使用==比较; [2.2]对于对象引用域,递归调用equals方法;(例如String对象的引用,可以直接调用它的hashCoder()) [2.3]对于float域,使用Float.floatToIntBits(afloat)转换为int,再使用==比较; [2.4]对于double域,使用Double.doubleToLongBits(adouble) 转换为int,再使用==比较; [2.5]对于数组域,调用Arrays.equals方法。
3.当改写equals()的时候,总是要改写hashCode() 根据一个类的equals方法(改写后),两个截然不同的实例有可能在逻辑上是相等的,但是,根据Object.hashCode方法,它们仅仅是两个对象。因此,违反了“相等的对象必须具有相等的散列码”。
4.设计hashCode() 重写equals时也要重写hashCode的原因: HashMap的底层处理机制是以数组的方式保存Map条目(Map Entry)的,这其中的关键是这个数组下标的处理机制:依据传入元素hashCode方法的返回值决定其数组的下标,
[1]把某个非零常数值,例如37(最好是素数),保存在int变量result中; [2]对于对象中每一个关键域f(指equals方法中考虑的每一个域):(例如下面的id,firstname和lastName) [2.1]boolean型,计算(f ? 0 : 1); [2.2]byte,char,short型,计算(int); [2.3]long型,计算(int) (f ^ (f>>>32)); [2.4]float型,计算Float.floatToIntBits(afloat); [2.5]double型,计算Double.doubleToLongBits(adouble)得到一个long,再执行[2.3]; [2.6]对象引用,递归调用它的hashCode方法; (例如String对象的引用,可以直接调用它的hashCoder()) [2.7]数组域,对其中每个元素调用它的hashCode方法。 [3]将上面计算得到的散列码保存到int变量c,然后执行 result = 37 * result + c; [4]返回result。
来看看这个例子,让我们创建一个简单的自定义类Employee 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class Employee { private Integer id; private String firstname; private String lastName; private String department; public Integer getId () { return id; } public void setId (Integer id) { this .id = id; } public String getFirstname () { return firstname; } public void setFirstname (String firstname) { this .firstname = firstname; } public String getLastName () { return lastName; } public void setLastName (String lastName) { this .lastName = lastName; } public String getDepartment () { return department; } public void setDepartment (String department) { this .department = department; } }
上面的Employee类只是有一些非常基础的属性和getter、setter.现在来考虑一个你需要比较两个employee的情形。
1 2 3 4 5 6 7 8 9 10 11 public class EqualsTest { public static void main (String[] args) { Employee e1 = new Employee(); Employee e2 = new Employee(); e1.setId(100 ); e2.setId(100 ); System.out.println(e1.equals(e2)); } }
毫无疑问,上面的程序将输出false,但是,事实上上面两个对象代表的是通过一个employee。真正的逻辑希望我们返回true。 为了达到这个目的,我们需要重写equals方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public boolean equals (Object o) { if (o == null ) { return false ; } if (o == this ) { return true ; } if (this .getClass() != o.getClass()) { return false ; } Employee e = (Employee) o; return (this .getId() == e.getId() && this .firstname.equals(e.firstname) && this .lastName.equals(e.lastName)); }
在上面的类中添加这个方法,EauqlsTest将会输出true。 So are we done?没有,让我们换一种测试方法来看看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import java.util.HashSet;import java.util.Set; public class EqualsTest { public static void main (String[] args) { Employee e1 = new Employee(); Employee e2 = new Employee(); e1.setId(100 ); e2.setId(100 ); System.out.println(e1.equals(e2)); Set<Employee> employees = new HashSet<Employee>(); employees.add(e1); employees.add(e2); System.out.println(employees); }
上面的程序输出的结果是两个。如果两个employee对象equals返回true,Set中应该只存储一个对象才对,问题在哪里呢? 我们忘掉了第二个重要的方法hashCode()。就像JDK的Javadoc中所说的一样,如果重写equals()方法必须要重写hashCode()方法。我们加上下面这个方法,程序将执行正确。1 2 3 4 5 6 7 8 9 @Override public int hashCode () { int result = 1 ; result = 37 * result + getId(); result = 37 * result + getFirstname().hashCoder(); result = 37 * result + getLastName().hashCoder(); return result; }
另外再补一个书上的示例: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 package com.zj.unit;import java.util.Arrays; public class Unit { private short ashort; private char achar; private byte abyte; private boolean abool; private long along; private float afloat; private double adouble; private Unit aObject; private int [] ints; private Unit[] units; public boolean equals (Object o) { if (o == null ) return false ; if (this == o) return true ; if (!(o instanceof Unit)) return false ; Unit unit = (Unit) o; return unit.ashort == ashort && unit.achar == achar && unit.abyte == abyte && unit.abool == abool && unit.along == along && Float.floatToIntBits(unit.afloat) == Float .floatToIntBits(afloat) && Double.doubleToLongBits(unit.adouble) == Double .doubleToLongBits(adouble) && unit.aObject.equals(aObject) && equalsInts(unit.ints) && equalsUnits(unit.units); } private boolean equalsInts (int [] aints) { return Arrays.equals(ints, aints); } private boolean equalsUnits (Unit[] aUnits) { return Arrays.equals(units, aUnits); } public int hashCode () { int result = 17 ; result = 37 * result + (int ) ashort; result = 37 * result + (int ) achar; result = 37 * result + (int ) abyte; result = 37 * result + (abool ? 0 : 1 ); result = 37 * result + (int ) (along ^ (along >>> 32 )); result = 37 * result + Float.floatToIntBits(afloat); long tolong = Double.doubleToLongBits(adouble); result = 37 * result + (int ) (tolong ^ (tolong >>> 32 )); result = 37 * result + aObject.hashCode(); result = 37 * result + intsHashCode(ints); result = 37 * result + unitsHashCode(units); return result; } private int intsHashCode (int [] aints) { int result = 17 ; for (int i = 0 ; i < aints.length; i++) result = 37 * result + aints[i]; return result; } private int unitsHashCode (Unit[] aUnits) { int result = 17 ; for (int i = 0 ; i < aUnits.length; i++) result = 37 * result + aUnits[i].hashCode(); return result; } }