覆盖equals方法和hashCode方法看似简单,但其实不然,如果没有按照jdk的通用规范去覆盖,那么基于这些约定的类将可能无法正常工作,例如基于散列的集合类HashMap和HashSet.
对于值类,我们通常需要覆盖Object.equals方法,因为我们希望通过equals方法知道它们在逻辑上是否相等.相应的这个类的实例可以被用作map的key,或者set的元素的时候才会表现出预期的行为. 对于"值类",枚举是个例外,因为枚举的每个值都是个单例.
在覆盖equals时,必须遵守JavaSE Object的规范:自反性(reflective), 对称性 (symmetric),传递性(transitive),一致性(consistent),对于任何非null的引用x, x.equals(null)返回false. 对于每个覆盖类equals的类,都应该有相应的单元测试去检查是否有没有违反上述约定。下面是个简单的例子:
- public class PhoneNumber {
- private int countryCode;
- private String nationalNumber;
- public PhoneNumber(){
- super();
- }
- public PhoneNumber(int countryCode, String nationalNumber) {
- super();
- this.countryCode = countryCode;
- this.nationalNumber = nationalNumber;
- }
- /**
- * @return the countryCode
- */
- public int getCountryCode() {
- return countryCode;
- }
- /**
- * @param countryCode the countryCode to set
- */
- public void setCountryCode(int countryCode) {
- this.countryCode = countryCode;
- }
- /**
- * @return the nationalNumber
- */
- public String getNationalNumber() {
- return nationalNumber;
- }
- /**
- * @param nationalNumber the nationalNumber to set
- */
- public void setNationalNumber(String nationalNumber) {
- this.nationalNumber = nationalNumber;
- }
- @Override
- public boolean equals(Object o){
- if(this == o){
- return true;
- }
- if(!(o instanceof PhoneNumber)){
- return false;
- }
- PhoneNumber pn = (PhoneNumber) o;
- return this.countryCode == pn.getCountryCode()
- && ( this.nationalNumber == null ? pn.nationalNumber == null : this.nationalNumber.equals(pn.nationalNumber));
- }
- }
- import static org.junit.Assert.*;
- import org.junit.Test;
- public class PhoneNumberTest {
- @Test
- public void testEqualsReflexive(){
- PhoneNumber pn1 = new PhoneNumber(86, "12345");
- assertTrue(pn1.equals(pn1));
- PhoneNumber pn2 = new PhoneNumber();
- assertTrue(pn2.equals(pn2));
- }
- @Test
- public void testEqualsSymmetric(){
- PhoneNumber pn1 = new PhoneNumber(86, "12345");
- PhoneNumber pn2 = new PhoneNumber(86, "12345");
- assertEquals(pn1.equals(pn2), pn2.equals(pn1));
- }
- @Test
- public void testEqualsTransitive(){
- PhoneNumber pn1 = new PhoneNumber(86, "12345");
- PhoneNumber pn2 = new PhoneNumber(86, "12345");
- PhoneNumber pn3 = new PhoneNumber(86, new String("12345"));
- assertTrue(pn1.equals(pn2));
- assertTrue(pn2.equals(pn3));
- assertTrue(pn1.equals(pn3));
- }
- @Test
- public void testEqualsConsistent(){
- PhoneNumber pn1 = new PhoneNumber(86, "12345");
- PhoneNumber pn2 = new PhoneNumber(86, "12345");
- for(int i=0; i<10 ; i++){
- assertTrue(pn1.equals(pn2));
- }
- }
- @Test
- public void testEqualsWithNull(){
- PhoneNumber pn1 = new PhoneNumber(86, "12345");
- assertFalse(pn1.equals(null));
- }
- }
当然还有一些实现高质量equals方法的诀窍:
1. 使用==操作符检查"参数是否为正确的引用"
2. 使用instanceof检查类型
3. 把参数转化为正确的类型
4. 选择逻辑比较的关键域,注意比较的顺序,primitive的比较可以放在前面,或者最有可能不一致性的域
5. 如果有double,float类型,用Double.compare,Float.compare比较
6. 覆盖equals重要覆盖hashCode
如前文所述,在覆盖了equals方法的类中,也必须覆盖hashCode方法。否则违反了Object.hashCode的通用约定会导致该类无法和基于散列的集合(HashMap,HashSet和HashTable)一起正常使用。
如下约定内容摘自Object规范:
1. 在应用程序中,只要对象的euqals方法的比较操作所用的信息没有修改,那么对于同一个对象的调用多次hashCode,必须始终如一返回同一个哈希值。
2. 如果两个对象通过equals比较相等,那么它们的哈希值相同。
3. 如果两个对象通过euqals比较不等,他们的哈希值可能相同,取决于hashCode的实现,由此散列表的性能也会有区别。
以前面的PhoneNumber类为例,编写了如下的测试用例:
- @Test
- public void testHashCode(){
- PhoneNumber pn1 = new PhoneNumber(86, "12345");
- PhoneNumber pn2 = new PhoneNumber(86, "12345");
- Map<PhoneNumber,String> map = new HashMap<PhoneNumber,String>();
- map.put(pn1, "12345");
- assertNotNull(map.get(pn2));
- }
发现测试失败了,但是两个对象通过equals比较是相等的。由于并没有覆盖hashCode方法导致两个相等的对象不能获得相同的散列码。根据约定重写hashCode:
- @Override
- public int hashCode(){
- int result = 17;
- result = 31 * result + countryCode;
- if(nationalNumber != null)
- result = 31 * result + nationalNumber.hashCode();
- return result;
- }
好的散列函数通常倾向于"为不相等的对象产生不相等的散列码", 否则会引起冲突,使散列表想链表退化。计算是可以把冗余的域排除在外。注意不要试图从散列码计算中排除关键域来提高性能。
相关推荐
Lombok是一个Java库,能够自动生成Getter、Setter、equals、hashCode和toString等方法,从而减少Java类的代码量。在本项目中,我们使用Lombok来简化Java类的编写。例如,我们可以使用@Data注解来生成Getter和Setter...
关于内存管理和字符串常量池,`"abc" + "def"`会直接在常量池中合并为一个字符串,而`new String(s1)`则在堆上创建新的对象,所以`s1 == s2`为`false`,但`s1.equals(s2)`为`true`。 在内存占用方面,题目中提到了...
equals() 是 Java 中的一个方法,用于比较对象是否相等。它是 Object 类的方法,在许多类中都可以使用。 在默认情况下,equals() 方法用于比较两个对象的引用是否相等,即判断它们是否指向同一个内存地址。这是通过...
java代码-使用java解决实现Student类的equals重载函数的源代码 ——学习参考资料:仅用于个人学习使用!
在实践中,为了编写正确的`equals`方法,通常建议使用`Objects.equals()`和`Objects.hash()`方法,它们可以避免空指针异常,并简化代码。同时,`equals`方法和`hashCode`方法应当一起重写,以确保对象相等时其哈希码...
"shallow-equals" 是一个专门用于浅层比较的开源库,它为开发者提供了便捷的方式来判断两个数据结构是否在表面级别(即只检查第一层属性)上相同。这个库尤其适用于那些性能敏感的场景,例如在React组件的...
在 Java 中,equals 方法是一个非常重要的方法,它用于判断两个对象是否相等,而不是判断两个对象的引用是否相同。在 Object 基类中,equals 方法的实现是使用“==”操作符来比较对象的引用,但是这并不满足实际需求...
Java 基础知识体系广泛且深入,涵盖了各种概念和技术。在Java中,选择合适的数据类型来表示价格是非常重要的。通常,如果不关心内存和性能,推荐使用`BigDecimal`类,因为它提供了精确的浮点数运算,适合处理货币...
Java 中 equals 和 == 的区别 Java 中的 equals 和 == 是两个不同的概念,很多开发者容易混淆它们。理解这两个概念的区别是非常重要的,因为它们对编程的正确性和性能都有很大的影响。 首先,我们需要了解 Java ...
在前端开发中,我们经常需要处理文件和目录的操作,例如读取、写入、比较等。"fs-equals"是一个专门为前端开发者设计的开源库...通过阅读源代码,你可以学习到更多关于文件系统操作、错误处理以及异步编程的最佳实践。
### Java中的`==`与`equals`方法的区别详解 在Java编程中,比较对象的相等性是一个常见的需求,但很多初学者对于`==`运算符与`equals`方法的区别容易混淆。本文将深入探讨两者之间的差异,以及它们在不同场景下的...
java_equals用法,用来熟悉重写equals方法的
计算机后端-Java-Java核心基础-第24章 集合01 23. 关于hashCode()和equals()的重写.avi
### Java中equals方法隐藏的陷阱 在Java编程中,正确实现`equals`方法至关重要,它不仅影响对象的比较逻辑,还直接关系到集合类(如`HashSet`、`HashMap`等)的行为。本文将深入探讨Java中`equals`方法的一些常见...
计算机后端-Java-Java核心基础-第14章 面向对象06 14. 重写equals().avi
【Java面试题】equals与==的区别
计算机后端-Java-Java核心基础-第14章 面向对象06 13. equals()的使用.avi
java8 集合源码分析 java基础复习 [TOC] 一、集合 1.Iterator 2.Collection 2.1 List--->有序、有索引、元素可重复 1.ArrayList: 底层是数组结构、查询快、增删慢、不同步 添加第一个元素的时候,创建默认个数是...
计算机后端-Java-Java核心基础-第14章 面向对象06 15. 总结==与equals().avi