equals hashCode

@wanqiuz 2018-06-03 08:13:41发表于 wanqiuz/blog-articles

1. equals编写指导

1.1. 简介

equals默认实现方式如下,只有this 和 obj引用同一个对象,才会返回true。

public boolean equals(Object obj) {
    return this == obj;
}

而我们往往需要用equals来判断 2个对象是否等价,而非验证他们的唯一性。这样我们在实现自己的类时,就要重写equals。

1.2. equals约定

  • 自反性: x.equals(x) 一定是true
  • 对null: x.equals(null) 一定是false
  • 对称性: x.equals(y) 和 y.equals(x)结果一致
  • 传递性: a 和 b equals , b 和 c equals,那么 a 和 c也一定equals。
  • 一致性: 在某个运行时期间,2个对象的状态的改变不会不影响equals的决策结果,那么,在这个运行时期间,无论调用多少次equals,都返回相同的结果。

1.3. 如何编写equals

  • 首先传入的比较对象的引用和this做比较,这样做是为了 save time ,节约执行时间,如果this 和 obj是对同一个堆对象的引用,那么,他们一定是equals 的。
  • 接着,判断obj是不是为null,如果为null,一定不equals,因为既然当前对象this能调用equals方法,那么它一定不是null,非null 和 null当然不等价。
  • 然后,比较2个对象的运行时类,是否为同一个类。不是同一个类,则不equals。getClass返回的是 this 和obj的运行时类的引用。如果他们属于同一个类,则返回的是同一个运行时类的引用。注意,一个类也是一个对象。
    有些程序员使用下面的第二种写法替代第一种比较运行时类的写法。应该避免这样做。
if((obj == null) || (obj.getClass() != this.getClass())) 
     return false; 

if(!(obj instanceof Test)) 
     return false; // avoid 避免!

它违反了公约中的对称原则。
例如:假设Dog扩展了Aminal类。

dog instanceof Animal      得到true
animal instanceof Dog      得到false

这就会导致

animal.equls(dog) 返回true
dog.equals(animal) 返回false

仅当Test类没有子类的时候,这样做才能保证是正确的。
2、按照第一种方法实现,那么equals只能比较同一个类的对象,不同类对象永远是false。但这并不是强制要求的。一般我们也很少需要在不同的类之间使用equals。

3、在具体比较对象的字段的时候,对于基本值类型的字段,直接用 == 来比较(注意浮点数的比较,这是一个坑)对于引用类型的字段,你可以调用他们的equals,当然,你也需要处理字段为null 的情况。对于浮点数的比较,我在看Arrays.binarySearch的源代码时,发现了如下对于浮点数的比较的技巧:

if ( Double.doubleToLongBits(d1) == Double.doubleToLongBits(d2) ) //d1 和 d2 是double类型
if(  Float.floatToIntBits(f1) == Float.floatToIntBits(f2)  )      //f1 和 f2 是d2是float类型

4、并不总是要将对象的所有字段来作为equals 的评判依据,那取决于你的业务要求。比如你要做一个家电功率统计系统,如果2个家电的功率一样,那就有足够的依据认为这2个家电对象等价了,至少在你这个业务逻辑背景下是等价的,并不关心他们的价钱啊,品牌啊,大小等其他参数。

5、最后需要注意的是,equals 方法的参数类型是Object,不要写错!

2. hashCode编写指导

2.1. 简介

public int hashCode()
这个方法返回对象的散列码,返回值是int类型的散列码。
对象的散列码是为了更好的支持基于哈希机制的Java集合类,例如 Hashtable, HashMap, HashSet 等。

2.2。 hashCode约定

  • 重写了euqls方法的对象必须同时重写hashCode()方法。
  • 如果2个对象通过equals调用后返回是true,那么这个2个对象的hashCode方法也必须返回同样的int型散列码
  • 如果2个对象通过equals返回false,他们的hashCode返回的值允许相同。(然而,程序员必须意识到,hashCode返回独一无二的散列码,会让存储这个对象的hashtables更好地工作。)

参与equals函数的字段,也必须都参与hashCode 的计算。

2.3. 如何编写equals

  1. 把某个非零的常数值,保存在一个名为result的int类型的常量中
  2. 属性域哈希码的计算
重要字段var的类型 他生成的hash分量
byte, char, short , int (int)var
long (int)(var ^ (var >>> 32))
boolean var?1:0
float Float.floatToIntBits(var)
double long bits = Double.doubleToLongBits(var);分量 = (int)(bits ^ (bits >>> 32));
引用类型 (null == var ? 0 : var.hashCode())
数组 每个元素依次hashCode
  1. 代入公式result = result * 31 + c
  2. 返回result
    比如:
@Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + name.hashCode();
        result = 31 * result + age;
        result = 31 * result + (int)(friendNumber^(friendNumber>>32));
        result = 31 * result + Float.floatToIntBits(cash);
        result = 31 * result + (int)(Double.doubleToLongBits(wealth)^(Double.doubleToLongBits(wealth)>>32));
        result = 31 * result + (isMarry ? 1 : 0);
        return result;
    }