一次性搞清楚equals和hashCode

Obejct类是Java中的万类之祖,其中,equals和hashCode是2个非常重要的方法。

下面开始刨析。

public boolean equals(Object o);

Object类中默认的实现方式是: return this == o。即是说,只有this和o引用同一个对象,才会返回true。
而我们往往需要用equals来判断两个对象是否等价,而非验证他们的唯一性。这样我们在实现自己的类时,就要重写equals。

按照约定,equals要满足以下规则:
  1.自反性:x.equals(x) 一定是true
  2.对null: x.equals(null) 一定是false
  3.对称性: x.equals(y)和y.equals(x)结果一致
  4.传递性: a和b equals,b和c equals,那么a 和 c也一定equals
  5.一致性:多次调用x.equals(y)都应该一致返回true或false

 一个例子
 
class Test{
    private int num;
    private String data;

    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if((obj == null) || (obj.getClass() != this.getClass()))
            return false;
        
        Test test = (Test) obj;
        return num == test.num && (data == this.data || (data != null && data.equals(test.data)))
    }       
}

  Test类对象有2个字段,num和data,这2个字段代表了对象的状态,他们也用在equals方法中作为评判的依据。

  在第8行,传入的比较对象的引用和this做比较,这样做是为了save time,节约执行时间,如果this和obj是对同一个对象的引用,那么,他们一定是equals的。

  接着,判断obj是不是为nul,如果为null,一定不equals,因为既然当前对象this能调用equals方法,那么它一定不为null,非null和null当然不等价。

  然后,比较2个对象的运行时类,是否为同一个类。不是同一个类,则不equals。getClass返回的是this和obj的运行时类的引用。如果他们属于同一个类,则返回的是同一个运行时类的引用。注意一个类也是一个对象。 

  1、一些程序员使用下面的第二种写法替代第一种比较运行时类的写法。应该避免这样做。

if((obj) == null) || (obj.getClass() != this.getClass()))
    return false;

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

  它违反了公约中的对称原则。

  例如:假设Dog扩展了Animal类。

  dog instanceof Animal 得到true

  animal instanceof Dog 得到false

  这就会导致

  animal.equals(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 是float类型

 

public int hashCode()

  这个方法返回对象的散列码,返回值是int类型的散列码。

  对象的散列码是为了更好的支持基于哈希机制的java集合类,例如 HashTable,HashMap,HashSet等。

  关于hashCode方法,一致的约定是:

  1.重写了equals方法的对象必须同时重写hashCode()方法。

  2.如果2个对象通过equals调用返回是true,那么这2个对象的hashCode()方法也必须返回同样的int型散列码。

  3.如果2个对象通过equals返回false,他们的hashCode返回的值允许相同。(然而,程序员必须意识到,hashCode返回独一无二的散列码,会让存储这个对象的hashtables更好的工作。)

在上面的例子中,Test类对象有2个字段,num和data,这2个字段代表了对象的状态,他们也用在equals方法中作为评判的依据。那么,在hashCode方法中,这2个字段也要参与hash值的运算,作为hash运算的中间参数。这点很关键,这是为了遵守:2个对象equals、那么hashCode一定相同规则。也就是说,参与equals函数的字段,也必须都参与hashCode的计算。

  相比于equals公认实现约定,hashCode的公约要求是很容易理解的。有2个重点是hashCode方法必须遵守的。约定的第3点,其实就是第2点的细化,下面我们就来看看对hashCode方法的一致约定的要求。

  第一:在某个运行期间,只要对象的(字段的)变化不会影响equals方法的决策结果,那么,在这个期间,无论调用多少次hashCode,都必须返回同一个散列码。

  第二:通过equals调用返回ture的2个对象的hashCode一定一样。

  第三:通过equals返回false的2个对象的散列码不需要不同,也就是他们的hashCode方法的返回值出现相同的情况。

 

转自: https://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&mid=2247486436&idx=1&sn=f1e58b5db2704029b1e23103056a6a78&chksm=ebd634c8dca1bdde69ce66c9ae9b1c51ce8660242b81828a6f4ad3f7118ce4b2f5dad7f55609&scene=21#wechat_redirect

posted @ 2020-11-15 19:02  V吾将上下而求索V  阅读(111)  评论(0编辑  收藏  举报