什么时候需要重写equals方法

本文深入探讨了散列表的工作原理及hashCode方法的重要性。通过具体示例解析了hashCode和equals方法在散列数据结构中的作用,并解释了它们对于提高查找效率的意义。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实例:

@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Question)) {
return false;
}
Question question = (Question)o;
return question.question_id == this.question_id;
}

@Override
public int hashCode() {
return this.question_id;
}


经常会有这样的操作,hibernate拿到1对多的关系集合,进行删除操作.那么这个时候一般是HashSet,那么只需要重写hashCode方法。

代码示例:

try {
if (zhuanjia != null) {
Hibernate.initialize(user.getGzzhuanjia());
Set<Customer> gzZhuanjia = user.getGzzhuanjia();
gzZhuanjia.remove(zhuanjia); //删除Set集合中的对象,只需要重写hashcode方法。
user.setGzzhuanjia(gzZhuanjia);
customerDao.update(user);
return ServiceConfig.returnj("", null);
} else {
return ServiceConfig.returnj("未查找到需要取消关注的专家", null);
}
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();// 手动回滚
return ServiceConfig.returnj("系统错误", null);
}

今天突然碰到了这样的问题,代码如下:

Map<Question,List<QuestionAnswer>> map = new HashMap<Question, List<QuestionAnswer>>();

for (int i = 0; i < answerList.size(); i++) {
answer = answerList.get(i); // 获取当前回答对象
theQuestion = answer.getQuestion();// 获取回答对象所属问题
if (theQuestion != null) {
mapValue = map.get(theQuestion); // 通过指定key获取mapValue
// 如果当前key未查出value,说明map中不存在,那么添加一条key value,如果key查出了value,说明map已经存在,直接添加一条value的值。
if (mapValue == null || mapValue.size() == 0) {
mapValue = new ArrayList<QuestionAnswer>();
}
mapValue.add(answer);
map.put(theQuestion, mapValue);// 更新当前key的value
}
}

如题:

question对象为key,如何也获取不到,经过网上的一番查找,如下解释,才知道需要重写equals方法,说实话我没看懂,大致看了第一行和第二行。

大师讲解:

你的对象想放到Set集合或者是想作为Map的key时(非散列的Set和Map,例如TreeSet,TreeMap等),那么你必须重写equals()方法,这样才能保证唯一性。对于良好的编程风格而言,你应该在重写equals()方法的同时,也重写hashCode()方法。

再说说必须重写hashCode()的情况: 
    如果你的对象想放进散列存储的集合中(比如:HashSet,LinkedHashSet)或者想作为散列Map(例如:HashMap,LinkedHashMap等等)的Key时,在重写equals()方法的同时,必须重写hashCode()方法。 


这里我想给楼主讲讲sun的设计者为什么需要设计hashcode方法,也许这样你就应该知道什么时候该重写它了。 
数据结构有一种为了提高查找的效率而存在的数据结构——散列表,散列表其实是普通数组概念的推广,因为可以对数组进行直接寻址,故可以再O(1)时间内访问数组的任意元素,thinking in java中有个对hashmap简单的实现,我们来看看你就明白了: 

Java代码  收藏代码
  1. //: containers/SimpleHashMap.java  
  2. // A demonstration hashed Map.  
  3. import java.util.*;  
  4. import net.mindview.util.*;  
  5.   
  6. public class SimpleHashMap<K,V> extends AbstractMap<K,V> {  
  7.   // Choose a prime number for the hash table  
  8.   // size, to achieve a uniform distribution:  
  9.   static final int SIZE = 997;  
  10.   // You can't have a physical array of generics,  
  11.   // but you can upcast to one:  
  12.   @SuppressWarnings("unchecked")  
  13.   LinkedList<MapEntry<K,V>>[] buckets =  
  14.     new LinkedList[SIZE];[color=red]//List数组里每项是个List,数组下标是hashcode方法的返回值再经过散列函数得到的,相当于散列表的关键字,它亦代表着每个对象的关键字。(不能显示new一个泛型数组,但是你可以new一个泛型数组的引用,如有需要以后可以将普通数组转型为泛型数组)。[/color]  
  15.   public V put(K key, V value) {[color=red]//将这个对键值放进hashmap中。[/color]  
  16.     V oldValue = null;  
  17.     int index = Math.abs(key.hashCode()) % SIZE;[color=red]//这里就是通过散列函数对hashcode的返回值处理得到一个关键字,它代表了对象在数组里的位置,既是数组下标。[/color]  
  18.     if(buckets[index] == null)  
  19.       buckets[index] = new LinkedList<MapEntry<K,V>>();[color=red]//如果是第一次散列到这个数组下标,那么就新生成一个LinkedList,可以看到它里面保存的是MapEntry<K,V>键和值。[/color]  
  20.     LinkedList<MapEntry<K,V>> bucket = buckets[index];[color=red]//将这个LinkedList赋值给一个bucket(桶),以后就直接在这个bucket进行操作。[/color]  
  21.     MapEntry<K,V> pair = new MapEntry<K,V>(key, value);  
  22.     boolean found = false;  
  23.     ListIterator<MapEntry<K,V>> it = bucket.listIterator();  
  24.     while(it.hasNext()) {  
  25.       MapEntry<K,V> iPair = it.next();  
  26.       if(iPair.getKey().equals(key)) {[color=red]//如果已经存在同一个键值,那么就更新value。[/color]  
  27.         oldValue = iPair.getValue();  
  28.         it.set(pair); // Replace old with new  
  29.         found = true;  
  30.         break;  
  31.       }  
  32.     }  
  33.     if(!found)  
  34.       buckets[index].add(pair);[color=red]//如果是一个新的键值,那么直接添加到这个LinkedList中。[/color]  
  35.     return oldValue;  
  36.   }  
  37.   public V get(Object key) {[color=red]//看hashmap是如何凭借hashcode方法快速定位到键值。[/color]  
  38.     int index = Math.abs(key.hashCode()) % SIZE;[color=red]//与put方法中的作用一样,生成数组下标,因为我存的时候就是存到这个地方的,那么我取的时候直接访问buckets[index]。[/color]  
  39.     if(buckets[index] == nullreturn null;[color=red]//直接访问这个数组下标的LinkedList,如果为null,则返回。[/color]  
  40.     for(MapEntry<K,V> iPair : buckets[index])[color=red]//为什么要用LinkedList,因为hashcode方法产生的散列码不能完全确定一个对象,也就是说会和其他对象发生“碰撞”,即散列到同一个数组下标,解决这个问题的组号办法就是定义一个List把它们保存起来,但是在这个List中,我们必须保证能用equals方法确定对象的身份,这也就是为什么很多人说hashcode()相等,equals()不一定相等,而equals()相等的两个对象,hashcode()一定相等。所以这里要直接在LinkedList执行线性查找。[/color]  
  41.       if(iPair.getKey().equals(key))  
  42.         return iPair.getValue();  
  43.     return null;  
  44.   }  
  45.   public Set<Map.Entry<K,V>> entrySet() {  
  46.     Set<Map.Entry<K,V>> set= new HashSet<Map.Entry<K,V>>();  
  47.     for(LinkedList<MapEntry<K,V>> bucket : buckets) {  
  48.       if(bucket == nullcontinue;  
  49.       for(MapEntry<K,V> mpair : bucket)  
  50.         set.add(mpair);  
  51.     }  
  52.     return set;  
  53.   }  
  54.   public static void main(String[] args) {  
  55.     SimpleHashMap<String,String> m =  
  56.       new SimpleHashMap<String,String>();  
  57.     m.putAll(Countries.capitals(25));  
  58.     System.out.println(m);  
  59.     System.out.println(m.get("ERITREA"));  
  60.     System.out.println(m.entrySet());  
  61.   }  
  62. /* Output: 
  63. {CAMEROON=Yaounde, CONGO=Brazzaville, CHAD=N'djamena, COTE D'IVOIR (IVORY COAST)=Yamoussoukro, CENTRAL AFRICAN REPUBLIC=Bangui, GUINEA=Conakry, BOTSWANA=Gaberone, BISSAU=Bissau, EGYPT=Cairo, ANGOLA=Luanda, BURKINA FASO=Ouagadougou, ERITREA=Asmara, THE GAMBIA=Banjul, KENYA=Nairobi, GABON=Libreville, CAPE VERDE=Praia, ALGERIA=Algiers, COMOROS=Moroni, EQUATORIAL GUINEA=Malabo, BURUNDI=Bujumbura, BENIN=Porto-Novo, BULGARIA=Sofia, GHANA=Accra, DJIBOUTI=Dijibouti, ETHIOPIA=Addis Ababa} 
  64. Asmara 
  65. [CAMEROON=Yaounde, CONGO=Brazzaville, CHAD=N'djamena, COTE D'IVOIR (IVORY COAST)=Yamoussoukro, CENTRAL AFRICAN REPUBLIC=Bangui, GUINEA=Conakry, BOTSWANA=Gaberone, BISSAU=Bissau, EGYPT=Cairo, ANGOLA=Luanda, BURKINA FASO=Ouagadougou, ERITREA=Asmara, THE GAMBIA=Banjul, KENYA=Nairobi, GABON=Libreville, CAPE VERDE=Praia, ALGERIA=Algiers, COMOROS=Moroni, EQUATORIAL GUINEA=Malabo, BURUNDI=Bujumbura, BENIN=Porto-Novo, BULGARIA=Sofia, GHANA=Accra, DJIBOUTI=Dijibouti, ETHIOPIA=Addis Ababa] 
  66. *///:~  

怎样?现在应该知道hashcode方法的作用了吧,它就是用来提高效率的,有句话说得好:为速度而散列。因为散列的Set和Map是基于hashcode方法来查找对象的,所以你在使用这些类的时候一定要覆盖hashcode方法,而非散列的Set和Map,例如TreeSet,TreeMap等,它们只需equals方法就可以唯一确定对象的身份。这样说,想必已经很清楚了吧。



<think>首先,用户的问题是:“我想了解在Java中正确重写equals方法需要遵循的步骤和注意事项 请问Java 重写 equals 方法的规范和步骤”。这基于之前的引用内容,我需要参考那些引用。 关键引用: - [^1]: 讨论了需要重写equals方法的类,如自定义类、集合类、字符串类等。 - [^2]: 强调了重写equals后必须重写hashCode方法,因为equals和hashCode需要一致。 - [^3]: 提供了重写equals方法的步骤示例代码。 - [^4]: 展示了重写equals的基本步骤代码。 我的回答必须: - 使用中文。 - 结构清晰,逐步解决问题。 - 确保真实可靠。 - 在回答中引用的段落末尾自然地添加引用标识,如[^1]。 - 生成相关问题。 - 对于数学表达式,使用$...$或$$...$$,但这个问题是关于Java的,可能没有太多数学内容,不过如果有,要遵守格式。 回答结构: 1. **引言**:解释为什么需要重写equals方法。 2. **步骤**:列出重写equals方法的规范步骤。 3. **注意事项**:包括必须重写hashCode、如何处理null和类型检查等。 4. **示例代码**:提供一个简单的Java代码示例。 5. **结论**:总结要点。 6. **相关问题**:生成2-3个相关问题。 内容要点基于引用: - 步骤一般包括: - 检查是否是同一个对象(==)。 - 检查参数是否为null。 - 检查类型是否匹配(使用getClass()或instanceof)。 - 比较关键属性。 - 注意事项: - 总是重写hashCode方法以保持一致性。 - 确保equals方法是对称的、自反的、传递的、一致的。 - 避免空指针异常。 - 示例代码:从引用[^3]或[^4]中提取。 生成相关问题: - 例如:如何重写hashCode方法?为什么重写equals时必须重写hashCode?equals方法的最佳实践是什么? 确保回答可靠:基于Java官方文档和常见实践。 最终输出格式: - 使用Markdown。 - 代码块用```java```。 - 引用标识自然添加。 - 相关问题部分以“§§相关问题§§”开头。</think>### Java中重写equals方法的规范和步骤 在Java中,`equals`方法用于比较两个对象是否逻辑相等。默认情况下(从`Object`类继承),它使用内存地址比较(即`==`),这通常不符合业务需求。因此,在自定义类中,我们需要重写`equals`方法以确保基于对象的属性进行比较。以下是重写`equals`方法的完整规范和步骤,基于Java最佳实践和常见设计原则。 #### 1. **为什么需要重写equals方法?** - 默认的`Object.equals`仅比较对象引用,而非内容。例如,对于自定义类(如`Student`),我们希望根据ID、姓名等属性判断相等性。 - 重写`equals`是确保对象在集合(如`HashSet`或`HashMap`)中正确工作的基础,避免重复元素问题[^1]。 - 注意:重写`equals`后**必须同时重写`hashCode`方法**,以维护`equals`和`hashCode`的契约(即如果两个对象`equals`返回`true`,则它们的`hashCode`必须相同)[^2]。 #### 2. **重写equals方法的规范步骤** 重写`equals`方法需遵循以下步骤,确保正确性、一致性和健壮性: 1. **检查对象引用是否相同**:使用`==`比较当前对象与参数对象。如果相同,直接返回`true`(优化性能)。 2. **检查参数是否为null**:如果参数为`null`,返回`false`,因为非null对象不可能等于null。 3. **检查类型是否兼容**:使用`getClass()`或`instanceof`确保参数对象是当前类或其子类的实例。如果类型不匹配,返回`false`。 4. **类型转换和属性比较**:将参数对象强制转换为当前类类型,然后逐一比较关键属性(如ID、名称等)。使用`Objects.equals()`处理可能为null的属性。 5. **确保方法一致性**:equals方法必须满足: - **自反性**:`x.equals(x)`为true。 - **对称性**:如果`x.equals(y)`为true,则`y.equals(x)`必须为true。 - **传递性**:如果`x.equals(y)`为true且`y.equals(z)`为true,则`x.equals(z)`必须为true。 - **一致性**:多次调用结果相同(不依赖可变状态)。 - **非null性**:`x.equals(null)`返回false。 #### 3. **注意事项** - **必须重写hashCode**:否则,当对象用于`HashMap`等集合时,会导致不一致行为(例如,equals相等但hashCode不同,导致集合无法正确识别重复元素)[^2]。 - **避免空指针异常**:在比较属性时,使用`Objects.equals(a, b)`代替`a.equals(b)`,以安全处理null值。 - **类型检查选择**: - 使用`getClass()`确保严格类型匹配(适用于final类)。 - 使用`instanceof`允许子类比较(适用于可继承类)。 - **性能优化**:先比较高性能属性(如基本类型),再比较低性能属性(如字符串)。 - **单元测试**:编写测试用例验证equals方法的对称性、传递性等。 #### 4. **示例代码** 以下是一个自定义`Student`类重写`equals`和`hashCode`的完整示例(基于引用[^3]和[^4]): ```java import java.util.Objects; public class Student { private String name; private int id; private int age; // 构造方法等省略... @Override public boolean equals(Object o) { // 步骤1: 检查引用相同 if (this == o) return true; // 步骤2: 检查null和类型兼容(使用getClass()确保严格类型) if (o == null || getClass() != o.getClass()) return false; // 步骤3: 类型转换 Student student = (Student) o; // 步骤4: 比较关键属性(使用Objects.equals处理null) return id == student.id && age == student.age && Objects.equals(name, student.name); } @Override public int hashCode() { // 使用Objects.hash生成基于属性的hashCode return Objects.hash(name, id, age); } } ``` **解释**: - 在`equals`方法中: - `if (this == o)`快速处理相同对象。 - `if (o == null || getClass() != o.getClass())`确保类型安全。 - 属性比较使用基本类型直接比较和`Objects.equals`(避免NullPointerException)。 - 在`hashCode`方法中,`Objects.hash`自动生成一致的hash值[^3]。 #### 5. **常见错误和最佳实践** - **错误**:忘记重写`hashCode`,导致集合操作异常。 - **错误**:使用`instanceof`但不处理子类问题(如果允许子类比较,需确保子类也正确重写equals)。 - **最佳实践**:在IDE(如IntelliJ或Eclipse)中使用自动生成功能,减少手动错误。 - **应用场景**:主要在自定义类、值对象(如DTO)、或集合元素类中重写[^1]。 重写`equals`方法是Java对象模型的核心部分,确保逻辑相等性的一致性和可靠性。始终遵循上述步骤和规范,以避免常见陷阱[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值