为什么在Java中需要重写equals和hashCode方法?

528

最近我阅读了这篇Developer Works文档

该文档主要介绍了如何有效和正确地定义hashCode()equals()方法,但是我不明白为什么需要覆盖这两个方法。

我该如何决定高效地实现这些方法?


1
仅使用Case Override重写equals方法: 两个相同的对象将具有不同的哈希码 = 相同的对象进入不同的桶(重复)。 仅使用Case Override重写hashcode方法: 两个相同的对象将具有相同的哈希码 = 相同的对象进入同一个桶(重复)。 - VedantK
链接似乎已经失效了。我能获取IBM的开发者文档吗? - tahasozgen
31个回答

7

如果您不覆盖它们,那么您将使用Object中的默认实现。

鉴于实例相等性和哈希码值通常需要了解对象的组成部分,因此通常需要在您的类中重新定义它们以具有任何实际含义。


6
为了在像HashMap、Hashtable等集合中使用自己的类对象作为键,我们应该重写hashCode()和equals()方法,并且需要了解集合内部的工作方式。否则,会导致意外的错误结果。

5

1) 常见误区如下面的示例所示。

public class Car {

    private String color;

    public Car(String color) {
        this.color = color;
    }

    public boolean equals(Object obj) {
        if(obj==null) return false;
        if (!(obj instanceof Car))
            return false;   
        if (obj == this)
            return true;
        return this.color.equals(((Car) obj).color);
    }

    public static void main(String[] args) {
        Car a1 = new Car("green");
        Car a2 = new Car("red");

        //hashMap stores Car type and its quantity
        HashMap<Car, Integer> m = new HashMap<Car, Integer>();
        m.put(a1, 10);
        m.put(a2, 20);
        System.out.println(m.get(new Car("green")));
    }
}

绿色汽车未找到

2. hashCode()引起的问题

问题是由未重写hashCode()方法引起的。 equals()hashCode()之间的约定是:

  1. If two objects are equal, then they must have the same hash code.
  2. If two objects have the same hash code, they may or may not be equal.

    public int hashCode(){  
      return this.color.hashCode(); 
    }
    

5

在使用值对象时很有用。以下是来自Portland Pattern Repository的摘录:

值对象的例子包括数字、日期、货币和字符串等。通常,它们是小型对象,被广泛地使用。它们的身份基于它们的状态而不是它们的对象身份。这样,您可以拥有多个相同概念的值对象的副本。

因此,我可以拥有代表日期1998年1月16日的对象的多个副本。其中任何一个副本都将相互相等。对于这样一个小对象,通常更容易创建新的对象并移动它们,而不是依赖单个对象来表示日期。

值对象应该始终在Java中重写.equals()(或在Smalltalk中为=)。 (记得同时重写.hashCode()。)


5
class A {
    int i;
    // Hashing Algorithm
    if even number return 0 else return 1
    // Equals Algorithm,
    if i = this.i return true else false
}
  • put('key','value') 方法将使用 hashCode() 计算哈希值以确定桶,并使用 equals() 方法查找值是否已存在于桶中。如果不存在,则添加该值,否则将替换为当前值。
  • get('key') 方法将使用 hashCode() 查找 Entry(桶)首先,再使用 equals() 查找 Entry 中的值。

如果两者都被重写,

Map<A>

Map.Entry 1 --> 1,3,5,...
Map.Entry 2 --> 2,4,6,...

如果equals没有被覆盖

Map<A>

Map.Entry 1 --> 1,3,5,...,1,3,5,... // Duplicate values as equals not overridden
Map.Entry 2 --> 2,4,6,...,2,4,..

如果没有重写hashCode方法:
映射Map<A>
Map.Entry 1 --> 1
Map.Entry 2 --> 2
Map.Entry 3 --> 3
Map.Entry 4 --> 1
Map.Entry 5 --> 2
Map.Entry 6 --> 3 // Same values are Stored in different hasCodes violates Contract 1
So on...

哈希码相等契约

  1. 如果根据equal方法两个键相等,则它们应该生成相同的哈希码。
  2. 生成相同哈希码的两个键不一定相等(在上面的示例中,所有偶数生成相同的哈希码)。

4
假设您有一个聚合了另外两个类(B)和(C)的类(A),并且您需要在哈希表中存储(A)的实例。默认实现只允许区分实例,但不能通过(B)和(C)进行区分。因此,两个A的实例可能相等,但默认情况下无法以正确的方式进行比较。

4
我正在研究“如果只重写hashCode,则在调用myMap.put(first,someValue)时,它会获取first,计算其hashCode并将其存储在给定的bucket中。 然后,当您调用myMap.put(first,someOtherValue)时,根据Map文档,它应该替换第一个对象为第二个对象,因为它们相等(根据我们的定义)。 ”
我认为第二次向myMap添加内容时,应该是“second”对象,如myMap.put(second,someOtherValue)

4
考虑一桶全是黑色球的集合。您的工作是按以下方式给这些球着色,并将其用于适当的游戏:
对于网球 - 黄色,红色。 对于板球 - 白色。
现在桶里有三种颜色的球:黄色、红色和白色。现在您已经给它们上了色,只有您知道哪种颜色是为哪种游戏而涂上的。 给球着色 - 散列。 选择参加游戏的球 - 相等。
如果您进行了染色并且有人选择了板球或网球的球,他们不会介意颜色!

3
如果你覆盖了equals()但没有覆盖hashcode(),除非你或其他人将该类类型用于像HashSet这样的哈希集合中,否则你不会遇到任何问题。 之前已经有人清楚地多次解释了文档理论,我只是在这里提供一个非常简单的例子。
考虑一个需要自定义equals()含义的类:
    public class Rishav {

        private String rshv;

        public Rishav(String rshv) {
            this.rshv = rshv;
        }

        /**
        * @return the rshv
        */
        public String getRshv() {
            return rshv;
        }

        /**
        * @param rshv the rshv to set
        */
        public void setRshv(String rshv) {
            this.rshv = rshv;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Rishav) {
                obj = (Rishav) obj;
                if (this.rshv.equals(((Rishav) obj).getRshv())) {
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        }

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

    }

现在考虑这个主类:-
    import java.util.HashSet;
    import java.util.Set;

    public class TestRishav {

        public static void main(String[] args) {
            Rishav rA = new Rishav("rishav");
            Rishav rB = new Rishav("rishav");
            System.out.println(rA.equals(rB));
            System.out.println("-----------------------------------");

            Set<Rishav> hashed = new HashSet<>();
            hashed.add(rA);
            System.out.println(hashed.contains(rB));
            System.out.println("-----------------------------------");

            hashed.add(rB);
            System.out.println(hashed.size());
        }

    }

这将产生以下输出:-
    true
    -----------------------------------
    true
    -----------------------------------
    1

我对结果感到满意。但是如果我没有覆盖hashCode(),那么会导致噩梦,因为具有相同成员内容的Rishav对象将不再被视为唯一的,因为hashCode将是不同的,这是由默认行为生成的,以下是可能的输出:

    true
    -----------------------------------
    false
    -----------------------------------
    2

3

Java中的Equals和Hashcode方法

它们是java.lang.Object类的方法,该类是所有类(包括自定义类和在java API中定义的其他类)的超类。

实现:

public boolean equals(Object obj)

public int hashCode()

enter image description here

public boolean equals(Object obj)

此方法只是检查两个对象引用x和y是否引用同一个对象。即它检查x == y。

它是自反的:对于任何引用值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。

public int hashCode()

此方法返回调用此方法的对象的哈希码值。此方法将哈希码值作为整数返回,并支持基于哈希的集合类(如Hashtable、HashMap、HashSet等)的好处。必须在每个覆盖equals方法的类中重写此方法。

hashCode的一般契约是:

在Java应用程序的执行期间,每当在同一对象上多次调用它时,hashCode方法必须一致地返回相同的整数,前提是在对象上使用的equals比较中未修改任何信息。

这个整数不需要从一个应用程序的执行到另一个应用程序的执行保持一致。

如果两个对象根据equals(Object)方法是相等的,则在每个对象上调用hashCode方法必须产生相同的整数结果。

并不要求如果两个对象根据equals(java.lang.Object)方法是不相等的,则在每个对象上调用hashCode方法必须产生不同的整数结果。然而,程序员应该知道,为不相等的对象产生不同的整数结果可能会改善哈希表的性能。

只要它们相等,相等的对象必须产生相同的哈希码,但是不相等的对象不需要产生不同的哈希码。

资源:

JavaRanch

Picture

以上内容包含了关于IT技术的链接和图片。

该图片(视频链接)处于私密模式,请将其设为公开以观看。 - UdayKiran Pulipati

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接