重写equals方法不重写hashcode方法分析

本文深入探讨了Java中equals和hashCode方法的实现原理,强调了重写这些方法时需遵循的规则,包括自反性、对称性、传递性和一致性,并通过具体示例展示了如何正确重写equals和hashCode方法,以确保对象比较的正确性和散列表性能的提升。

equals源码:

 public static boolean equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));
    }
 public boolean equals(Object obj) {
        return (this == obj);
    }

可见,源码中的equals不重写的话,就是==

先重写equals方法:

加入有一个user类,忽略掉id属性,比较name和age,如果两个相等,则视为true。

重写equals方法需要遵循Java如下规则,否则编码行为会难以揣测:
  • 自反性:对于任意的对象x,x.equals(x)返回true(自己一定等于自己);
  • 对称性:对于任意的对象x和y,若x.equals(y)为true,则y.equals(x)亦为true;
  • 传递性:对于任意的对象x、y和z,若x.equals(y)为true且y.equals(z)也为true,则x.equals(z)亦为true;
  • 一致性:对于任意的对象x和y,x.equals(y)的第一次调用为true,那么x.equals(y)的第二次、第三次、第n次调用也均为true,前提条件是没有修改x也没有修改y;
  • 对于非空引用x,x.equals(null)永远返回为false。
写一下单元测试:
public class userTest {

    @Test
    public void equals1() {
        user x = new user(0,"刘三",18);
        user y = new user(4,"刘三",18);
        user z = new user(0,"刘三",18);
	/** 自反性:对于任意的对象x,x.equals(x)返回true(自己一定等于自己);*/
	  assertTrue(x.equals(x));
	  /** 对称性:对于任意的对象x和y,若x.equals(y)为true,则y.equals(x)亦为true;*/
        assertTrue(x.equals(y));
        assertTrue(y.equals(x));

	/**
        传递性:对于任意的对象x、y和z,若x.equals(y)为true且y.equals(z)也为true,则x.equals(z)亦为true;*/
        assertTrue(x.equals(y));
        assertTrue(y.equals(z));
        assertTrue(x.equals(y));
/**
        一致性:对于任意的对象x和y,x.equals(y)的第一次调用为true,那么x.equals(y)的第二次、第三次、第n次调用也均为true,前提条件是没有修改x也没有修改y;*/
        assertTrue(x.equals(y));

/**
        对于非空引用x,x.equals(null)永远返回为false。*/
        assertFalse(x.equals(null));
       
    }
}

代码

public class user {
 private  int id;
 private  String name;
 private  int age;

   user(int id,String name,int age){
       this.id =id;
       this.name=name;
       this.age =age;
   }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        user user = (user) o;
        return 
                age == user.age &&
                Objects.equals(name, user.name);
    }
}

hashcode 方法的通用约定

  • 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,那么,对该对象调用hashCode方法多次,它必须始终如一地返回 同一个整数。在同一个应用程序的多次执行过程中,这个整数可以不同,即这个应用程序这次执行返回的整数与下一次执行返回的整数可以不一致。
  • 如果两个对象根据equals(Object)方法是相等的,那么调用这两个对象中任一个对象的hashCode方法必须产生同样的整数结果。
  • 如果两个对象根据equals(Object)方法是不相等的,那么调用这两个对象中任一个对象的hashCode方法,不要求必须产生不同的整数结果。然而,程序员应该意识到这样的事实,对于不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table)的性能。
单元测试代码
/* -  在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,那么,对该对象调用hashCode方法多次,
       它必须始终如一地返回 同一个整数。在同一个应用程序的多次执行过程中,这个整数可以不同,
       即这个应用程序这次执行返回的整数与下一次执行返回的整数可以不一致。
        */
       assertTrue(x.hashCode()==x.hashCode()&&x.hashCode()==x.hashCode());
       /*- 如果两个对象根据equals(Object)方法是相等的,那么调用这两个对象中任一个对象的hashCode方法必须产生同样的整数结果。*/
        assertFalse(x.hashCode()==z.hashCode());
        assertFalse(x.hashCode()==y.hashCode());
       /* - 如果两个对象根据equals(Object)方法是不相等的,那么调用这两个对象中任一个对象的hashCode方法,
       不要求必须产生不同的整数结果。然而,程序员应该意识到这样的事实,对于不相等的对象产生截然不同的整数结果,
       有可能提高散列表(hash table)的性能。
       */ 

运行通过,发现不满足第二条。即两个相等的对象,hashcode不一样。

再看如下代码片段

HashMap<user,Integer> m = new HashMap<user,Integer>();
              m.put(x,10);
              m.put(y,20);
              m.put(z,30);
        System.out.println(m.get(new user(0,"刘三",18)));

运行结果是null;

  • map 中源码
 public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
get中比较的是hashcode。
而hashcode不相等。

重写hashCode()

 @Override
    public int hashCode() {
        int result = 17;
        result = 31*result + name!=null?name.hashCode():0;
        result = 31*result + age;
        return Objects.hash(name, age);
    }

满足第二条,且System.out.println(m.get(new user(0,“刘三”,18)));的输出为30

<think> 首先,用户的问题是基于给定的引用材料:"我想了解重写equals方法需要重写hashcode方法的原因"。引用包括四个来源:[^1], [^2], [^3], [^4],这些都讨论了为什么在Java中重写equals方法必须重写hashCode方法。 我的响应需要遵循系统级指令: - 所有行内数学表达式必须使用$...$格式,但在这个上下文中,似乎没有数学表达式,所以可能需要。 - 独立公式必须使用$$...$$格式并单独成段,同样,可能适用。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如:[^1]。 用户可见层的要求: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 关键点:用户想知道重写equals方法需要重写hashCode方法的原因。引用材料提供了原因: - [^1] 提到了hashCode重写的基本规则和两者之间的关系。 - [^2] 讨论了==与equals的区别,以及为什么必须重写hashCode。 - [^3] 强调违反了Java的对象契约如果重写hashCode。 - [^4] 提供了一个代码示例,显示如果重写hashCode,会导致一致的行为。 核心原因总结自引用: 1. **一致性**:equals方法定义了对象内容的相等性;hashCode方法返回对象的哈希码。在Java中,如果两个对象根据equals方法相等,它们的hashCode必须相等。如果遵守这个规则,在使用基于哈希的集合(如HashMap、HashSet),会导致问题。 2. **契约**:Java的Object类定义了equalshashCode方法的契约。重写equals方法必须重写hashCode以维持这个契约。 3. **问题示例**:如果只重写equals重写hashCode,两个对象可能equalshashCode同。这会导致在HashMap中,对象可能被放入错误的桶中,无法正常查找或导致重复添加。 结构响应: - **逐步解决**:从基础开始解释,逐步到原因。 - **引用**:在解释,自然地添加引用标识。 - **中文回答**:整个响应用中文。 - **生成相关问题**:在回答后添加相关问题部分。 响应大纲: 1. **引言**:简要介绍equalshashCode方法的作用。 2. **原因解释**: - 解释equals方法重写规则。 - 解释为什么需要一致性。 - 给出基于引用的具体原因。 3. **代码示例**:类似[^4],提供一个简单例子说明问题。 4. **总结**:强调重写的必要性。 5. **相关问题**:生成2-3个相关问题。 相关问题灵感: - 基于主题,如:如何正确重写hashCode方法?如果重写hashCode会有什么后果?在哪些场景下必须重写hashCode? 确保真实可靠:基于提供的引用和Java官方文档知识。 最终响应要用中文。 </think> ### 重写equals方法必须重写hashCode方法的原因 在Java中,`equals`方法用于比较两个对象的内容是否相等,而`hashCode`方法返回对象的哈希码值。根据Java的对象契约(Object Contract),重写`equals`方法必须同重写`hashCode`方法,否则会导致程序行为异常。主要原因包括以下几点: 1. **维持对象契约的一致性**: Java的`Object`类定义了`equals`和`hashCode`方法之间的契约:如果两个对象根据`equals`方法相等,那么它们的`hashCode`必须相等。反之,如果两个对象的`hashCode`等,则它们一定相等。如果遵守这个规则,会违反Java的通用约定,导致哈希相关集合(如`HashMap`、`HashSet`)工作正常[^3]。 - 例如,在`HashMap`中,对象存储和检索依赖于`hashCode`值来决定桶的位置。如果只重写`equals`而重写`hashCode`,两个内容相等的对象可能产生同的哈希码结果,造成对象无法被正确查找或重复存储。 2. **防止哈希相关集合的异常行为**: 在使用基于哈希的数据结构,`hashCode`方法用于快速定位对象。如果只重写`equals`方法重写`hashCode`,会导致以下问题: - **对象丢失**:在`HashSet`中,两个`equals`相等的对象可能被分配到同的桶中,导致集合无法识别它们是相同的对象,从而允许重复添加。 - **检索失败**:在`HashMap`中,尝试用键对象检索值,如果键的`hashCode`与存储匹配,即使`equals`返回`true`,也可能无法找到对应的值,引发逻辑错误[^1][^2]。 ```java // 示例:只重写equals重写hashCode的问题场景 import java.util.HashSet; public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } // 错误:没有重写hashCode public static void main(String[] args) { Person p1 = new Person("Alice", 30); Person p2 = new Person("Alice", 30); System.out.println(p1.equals(p2)); // 输出 true HashSet<Person> set = new HashSet<>(); set.add(p1); System.out.println(set.contains(p2)); // 可能输出 false,因为hashCode重写,哈希码同 } } ``` 3. **确保equalshashCode的逻辑等价性**: `hashCode`方法应基于对象中用于`equals`比较的属性计算哈希码。如果只重写`hashCode`而重写`equals`,或反之,会导致一致: - 两个对象`equals`相等,但`hashCode`同:违反契约,引发哈希集合故障。 - 两个对象`hashCode`相同,但`equals`相等:这是允许的(哈希冲突),但如果重写`equals`,可能无法正确处理冲突[^4]。 正确的实践是使用相同的属性集计算哈希码,例如通过`Objects.hash()`方法简化实现,确保两者逻辑一致[^3][^4]。 总结来说,重写`equals`必须重写`hashCode`,是为了遵守Java的对象契约,避免在哈希相关集合中出现可预测的行为。推荐使用IDE(如IntelliJ IDEA)自动生成`equals`和`hashCode`方法,或利用`Objects.equals()`和`Objects.hash()`来简化代码并提高健壮性[^3][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值