如何编写出高质量的 equals 和 hashcode 方法? (2)

再一次运行 Main 方法,你会发现还是 False ,这是为什么呢?我已经把判断两个对象相等的逻辑告诉程序了,不急,我们先来聊一聊哈希表吧,我们知道哈希表采用的是数组+链表的结构,每个数组上挂载着链表,链表的节点用来存储对象信息,而对象落到数组的位置由 hashcode()。所以当我们调用 HashSet 的 add(Object o) 方法时,首先会根据o.hashCode()的返回值定位到相应的数组位置,如果该数组位置上没有结点,则将 o 放到这里,如果已经有结点了, 则把 o 挂到链表末端。同理,当调用 contains(Object o) 时,Java 会通过 hashCode()的返回值定位到相应的数组位置,然后再在对应的链表中的结点依次调用 equals() 方法来判断结点中的对象是否是你想要的对象。

由于我们只重写了 equals 方法并没有重写 hashcode 方法,所以两篇文章的 hashcode 值不一样,这样映射到数组的位置就不一样,调用 set.contains(article1) 方法时,在哈希表中的情况可能如下图所示:

如何编写出高质量的 equals 和 hashcode 方法?

article 对象被映射到了数组下标为 0 的位置,article1 对象被映射到了数组下标为 6 的位置,所以没有找到返回 False。既然只重写 equals 方法不行,那么我们把 hashcode 方法也重写了。

2、重写 hashcode 方法

跟 equals 方法一样,我们也使用 idea 编辑器帮我们生成的 hashcode 方法,只需要做稍微的改动就可以,具体 hashcode 代码如下:

@Override public int hashCode() { return Objects.hash(url); }

重写好 hashcode 方法之后,再一次运行 Main 方法,这次得到的结果为 True,这会就是我们想要的结果了。重写 equals 和 hashcode 方法之后,在哈希表中的查找如下图所示:

如何编写出高质量的 equals 和 hashcode 方法?

首先 article1 对象也会被映射到数组下标为 1 的位置,在数组下标为 1 的位置存在 article 数据节点,所以会执行 article1.equals(article) 命令,因为我们重写了 Article 对象的 equals 方法,这个是否会判断两个 Article 对象的 url 属性是否相等,如果相等就返回 True,在这里显然是相等的,所以这里就返回 True,得到我们想要的结果。

如何编写 equals 和 hashcode 方法?

需要自己重写 equals 方法?好的,我这就重写,噼里啪啦的敲出了下面这段代码:

public boolean equals(Article o) { if (this == o) return true; if (o == null || !(o instanceof Article)) return false; return o.url.equals(url); }

这样写对吗?虽然里面的逻辑看上的没什么问题,但是 equals 方法的参数变成了Article。 其实你这跟重写 equals 方法没有半毛线关系,这完全是重新定义了一个参数类型为 Article 的 equals 方法,并没有去覆盖 Object 类中的 equals 方法。

那该如何重写 equals 方法呢?其实 equals 方法是有通用规定的,当你重写 equals 方法时,你就需要重写 equals 方法的通用约定,在 Object 中有如下规范: equals 方法实现了一个等价关系(equivalence relation)。它有以下这些属性:

自反性:对于任何非空引用 x,x.equals(x) 必须返回 true

对称性:对于任何非空引用 x 和 y,如果且仅当 y.equals(x) 返回 true 时 x.equals(y) 必须返回 true

传递性:对于任何非空引用 x、y、z,如果 x.equals(y) 返回 true,y.equals(z) 返回 true,则 x.equals(z) 必须返回 true

一致性:对于任何非空引用 x 和 y,如果在 equals 比较中使用的信息没有修改,则 x.equals(y) 的多次调用必须始终返回 true 或始终返回 false

非空性:对于任何非空引用 x,x.equals(null) 必须返回 false

现在我们已经知道了写 equals 方法的通用约定,那我们就参照重写 equals 方法的通用约定,再一次来重写 Article 对象的 equals() 方法。代码如下:

// 使用 @Override 标记,这样就可以避免上面的错误 @Override public boolean equals(Object o) { // 1、判断是否等于自身 if (this == o) return true; // 2、判断 o 对象是否为空 或者类型是否为 Article if (o == null || !(o instanceof Article)) return false; // 3、参数类型转换 Article article = (Article) o; // 4、判断两个对象的 url 是否相等 return article.url.equals(url); }

这一次我们使用了 @Override 标记,这样就可以避免我们上一个重写的错误,因为父类中并没有参数为 Article 的方法,所以编译器会报错,这对程序员来说是非常友好的。接下来我们进行了 自反性、非空性的验证,最后判断两个对象的 url 是否相等。这个 equals 方法就比上面那个要好很多,基本上没什么大毛病了。

在 effective-java 书中总结了一套编写高质量 equals 方法的配方,配方如下:

1、使用 == 运算符检查参数是否为该对象的引用。如果是,返回 true。

2、使用 instanceof 运算符来检查参数是否具有正确的类型。 如果不是,则返回 false。

3、参数转换为正确的类型。因为转换操作在 instanceof 中已经处理过,所以它肯定会成功。

4、对于类中的每个「重要」的属性,请检查该参数属性是否与该对象对应的属性相匹配。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyxsfw.html