`

为什么要复写equals()的同时最好复写hashcode()

阅读更多
我们都知道所有的类都从Object继承了equals()和hashcode(),先来看看equals(),和hashcode()在Object中的实现:
public boolean equals(Object obj) {
        return (this == obj);
    }

public native int hashCode();

可以知道equals比较的实际是引用的地址,hashcode实际是一个本地方法,可以理解为返回的是
一个具体的地址。在实际使用中如果我们要使用向HashSet这类不能重复添加的集合时就必须重写equals和hashcode方法,如果你不使用集合的话,自己定义一个一般的类一般情况下是不需要重写的。为什么要重写hashcode和equals呢?hashset的内部实际是一个HashMap我们先看一下在向HashMap中添加一个元素时实际发生了什么
public V put(K key, V value) {//在此映射中关联指定值与指定键。如果该映射以前包含了一个该键的映射关系,则旧值被替换。
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
void recordAccess(HashMap<K,V> m) {
        }

可以发现它首先调用key的hashcode()得到一个hash值,再调用indexFor(HashMap的底层也是用一个数组保存key和value的,有兴趣可以去看看)得到key的索引。下面将该索引对应的值赋给一个Entry(保存着key和value),e.hash == hash && ((k = e.key) == key || key.equals(k))将Entry中的key和要放入的比较,调用equals和要放入的元素比较。如果为真,用vlaue替换oldvalue,接下来记录这个值(实际重新new了一个HashMap)返oldvalue.如果为假,将HashMap长度+1,返回null。
上面分析put的过程发现,put方法内部实际上调用了对象的hashcode方法和equals方法。这就是为什么在重写equals的同时最好复写hashcode方法,如果重写equals而不重写hashcode方法会发送什么呢?在举例之前我们看看在java中,String和Integer这些预定义的包装类中已经帮我们重写了equals和hashcode:
public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = count;
            if (n == anotherString.count) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = offset;
                int j = anotherString.offset;
                while (n-- != 0) {
                    if (v1[i++] != v2[j++])
                        return false;
                }
                return true;
            }
        }
        return false;
    }
 public int hashCode() {
        int h = hash;
        if (h == 0 && count > 0) {
            int off = offset;
            char val[] = value;
            int len = count;

            for (int i = 0; i < len; i++) {
                h = 31*h + val[off++];
            }
            hash = h;
        }
        return h;
    }

可以发现String的equals实际比较的是对象的内容而不是对象引用的地址,hashcode方法是根据字符串的长度来生成hash值的。所以在实际使用的有的初学者可能会发现这样的问题:
 A a = new A("hello");
 A b = new A("hello");
 a.equals(b);//false
 String a = new String("hello");
 String b = new String("hello");
 a.equals(b);//true,A没有重写equals,而String帮我们重写了

回到上面的问题:假如重写equals而不重写hashcode方法会发什么?重写之后有什么变化
为了输出方便,我们这里使用HashSet(不重复的散列集)
public class EqualsAndHashcode {

	public static void main(String[] args) {
		HashSet h = new HashSet();
		A a1 = new A(1);//不作任何重写默认使用Object的
		A a2 = new A(1);// output:false false 2018699554,1311053135
		System.out.println(a1 == a2);
		System.out.println(a1.equals(a2));
		System.out.println(a1.hashCode() + "," + a2.hashCode());
		// 只复写euqals
		B b1 = new B(1);//output:true,118352462,1550089733
		B b2 = new B(1);//       false,118352462,865113938 
		B b3 = new B(2);//[cn.dx.cellection.B@5c647e05
                                //,cn.dx.cellection.B@33909752,
                                //cn.dx.cellection.B@70dea4e
		System.out.println(b1.equals(b2) + "," + b1.hashCode() + ","
				+ b2.hashCode());
		System.out.println(b1.equals(b3) + "," + b1.hashCode() + ","
				+ b3.hashCode());
		h.add(b1);
		h.add(b2);
		h.add(b3);
		System.out.println(h);
		// 同时重写code,和equals
		C c1 = new C(1);
		C c2 = new C(1);
		C c3 = new C(2);
		System.out.println(c1.equals(c2) + "," + c1.hashCode() + ","
				+ c2.hashCode());
		System.out.println(c1.equals(c3) + "," + c1.hashCode() + ","
				+ c3.hashCode());
		h = new HashSet();
		h.add(c1);
		h.add(c2);
		h.add(c3);
		System.out.println(h);
		                      //             true,2,2
                                    //               false,2,4
                                  //[cn.dx.cellection.C@2, cn.dx.cellection.C@4]
	}

}

class A {
	private int a;

	A(int i) {
		this.a = i;
	}

}

class B {
	private int a;

	B(int i) {
		this.a = i;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (null == obj)
			return false;
		if (obj instanceof B) {
			B other = (B) obj;
			if (this.a == other.a)
				return true;
		}
		return false;
	}

}
class C{
	private int a;
	C(int i){
		this.a = i;
	}
	@Override
	public boolean equals(Object obj) {
		if(this == obj)
			return true;
		if (null== obj)
			return false;
		if(obj instanceof C){
			C other = (C)obj;
			if(this.a == other.a)
				return true;
		}
		return false;
	}
	@Override
	public int hashCode() {
		// TODO Auto-generated method stub
		return a*2<<10%10;
	}
	

}
/**
false
false
2018699554,1311053135
true,118352462,1550089733
false,118352462,865113938
[cn.dx.cellection.B@5c647e05, cn.dx.cellection.B@33909752, cn.dx.cellection.B@70dea4e]
true,2,2
false,2,4
[cn.dx.cellection.C@2, cn.dx.cellection.C@4]

*/

从上面可以发现,如果不作任何复写默认使用Object的,只复写equals方法向hashset添加时都添加进去了,都复写不会添加重复的元素。所以,如果两个对象equals返回true,hashcode也应该是相同的,不然相同的元素也被添加进去了,违背了我们使用集合的初衷。
反之:两个元素equals返回true,hashcode不一定相等,同时都重写才相等。
     hashcode相等的两个元素equals一定为true,即充分不必要。
复写equal方法应该满足:
自反性:对于任何非空引用值 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,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
对于任何非空引用值 x,x.equals(null) 都应返回 false。
分享到:
评论

相关推荐

    Java重写equals同时需要重写hashCode的代码说明

    Java重写equals同时需要重写hashCode的代码说明,以及如何重写hashCode方法,此代码演示按照effective java书籍说明的重写思路。代码中演示了使用集合存储对象,并且对象作为key,需重写equals和hashCode.

    hashcode和equals方法

    equals()和hashcode()这两个方法都是从object类中继承过来的。当String 、Math、还有Integer、Double。。。。等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法.

    复写hashCode()方法,和equasl()方法

    ### 复写`hashCode()`方法与`equals()`方法 在Java编程中,`hashCode()`方法与`equals()`方法是对象比较中的两个非常重要的方法。它们主要用于判断对象是否相等以及对象的散列值,这对于集合类(如`HashSet`)来说...

    重写equals和hashcode方法_equals_重写equals和hashcode方法_

    - 当重写 `equals()` 时,总是应该同时重写 `hashCode()`。 - 避免在 `equals()` 方法中使用 `==` 来比较对象,除非你想比较的是引用。 - 在 `hashCode()` 方法中,应使用对象的非空关键属性来计算哈希码,以保证...

    HashCode相同equals不同的2位字符集合算法

    同时,一旦`hashCode()`返回相同,`equals()`应该正确地检查对象的详细内容以确认它们是否真的相等。 在实际编程中,通常推荐使用`Objects`类的`equals()`和`hashCode()`方法,它们提供了安全且一致的实现。例如: ...

    关于hashCode()和equals()的本质区别和联系

    为什么需要重写 hashCode() 和 equals() 方法 在 Java 中,有很多类忽略了 hashCode() 和 equals() 方法的默认实现,以便提供更深层次的语义可比性。例如,Integer 类重写了 equals() 方法,使得两个 Integer 对象...

    equals,hashcode,toString

    当两个对象通过`equals()`方法判断为相等时,它们的`hashCode()`方法返回值也应该相等。反之,如果两个对象不相等,它们的哈希码应该尽可能不同,以减少哈希冲突的概率。因此,重写`equals()`时,通常也需要重写`...

    Java理论与实践:hashCode()和equals()方法

    本文还介绍了定义对象的相等性、实施equals()和hashCode()的需求、编写自己的equals()和hashCode()方法。通过统一定义equals()和hashCode(),可以提升类作为基于散列的集合中的关键字的使用性。

    Java_重写equals()和hashCode()

    这就是为什么在设计类时,重写这两个方法是至关重要的,尤其是在实现集合类的元素或键值对时。 总之,理解并正确重写 `equals()` 和 `hashCode()` 方法对于编写高质量的Java代码至关重要,这直接影响到对象比较的...

    equals与hashCode方法讲解

    equals 与 hashCode 方法讲解 equals 方法和 hashCode 方法是 Java 语言中两个重要的方法,它们都是在 Object 类中定义的。equals 方法用于比较两个对象是否相等,而 hashCode 方法用于返回对象的哈希码。 在 Java...

    equals与hashCode在实际开发中的重写写法

    在重写 `hashCode()` 时,要确保它和 `equals()` 方法保持一致。一种常见做法是将对象的各个属性值进行位运算(如异或、加法等),并结合对象的类的哈希码,以生成一个独特的整数。 关联 `equals()` 和 `hashCode()...

    hashcode()和equals()

    在Java编程语言中,`hashCode()` 和 `equals()` 方法是两个非常重要的概念,尤其是在处理对象比较和哈希表(如 `HashMap` 或 `HashSet`)时。这两个方法来源于 `Object` 类,是所有Java类的基类,因此,每个自定义类...

    hashcode和equals的分析

    ### hashCode和equals方法详解 #### 一、hashCode方法解析 在深入探讨`hashCode`方法之前,我们需要了解Java集合框架的基本概念。Java集合框架主要包括两大类集合:`List`和`Set`。 - **List**:这是一个有序集合...

    java中hashcode()和equals()方法详解

    - 为了避免哈希冲突带来的性能问题,在覆盖`equals()`方法的同时,通常也需要覆盖`hashCode()`方法。 #### 实现示例 以下是一个简单的类`Person`,展示了如何正确覆盖`equals()`和`hashCode()`方法: ```java ...

    为什么重写equals方法,还必须要重写hashcode方法

    为什么重写equals方法,还必须要重写hashcode方法

    set接口经常用的hashCode和equals方法详解

    假设我们有一个自定义类`Person`,我们需要为其实现`hashCode`和`equals`方法: ```java public class Person { private String name; private int age; // 构造函数和其他方法省略... @Override public ...

    【面试】hashCode与equals两者之间的关系 / == 和equals / 为什么要重写equals方法 / 重写equals /hashcode方法 / 为什么要重写hashCode方法

    3、**为什么要重写equals()方法?** 默认情况下,`equals()`方法继承自`Object`类,比较的是对象的引用。在处理自定义类时,我们可能关心的是对象的属性是否相等,而不是它们的内存地址。因此,为了基于对象的内容...

    Java中equals,hashcode和==的区别

    "Java中equals、hashcode和==的区别" Java 中 equals、hashcode 和==的区别是 Java 编程语言中一个经常遇到的问题。这三个概念都是用来比较对象的,但是它们之间存在着本质的区别。 首先,==号是Java中的一个...

    为什么在重写 equals方法的同时必须重写 hashcode方法

    首先,根据键的 `hashCode` 定位到对应的桶,然后遍历桶中的条目,如果找到一个条目的键与要插入的键 `equals` 并且哈希值相同,那么就会覆盖旧的值,否则会在桶中添加新的条目。如果两个键的 `equals` 返回 true 但...

    java中hashcode和equals的详解.pdf

    Java 中的 hashCode 和 equals 方法详解 本文详细介绍了 Java 中的 hashCode 和 equals 方法,探讨了这两个方法的作用、实现机制和使用场景。通过对 hashCode 和 equals 方法的深入分析,我们可以更好地理解 Java ...

Global site tag (gtag.js) - Google Analytics