Java 了解 HashMap 中 equals 和 hashCode 的工作原理

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1894377/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 23:38:24  来源:igfitidea点击:

Understanding the workings of equals and hashCode in a HashMap

javahashmapequalshashcode

提问by andandandand

I have this test code:

我有这个测试代码:

import java.util.*;

class MapEQ {

  public static void main(String[] args) {
   Map<ToDos, String> m = new HashMap<ToDos, String>();
   ToDos t1 = new ToDos("Monday");
   ToDos t2 = new ToDos("Monday");
   ToDos t3 = new ToDos("Tuesday");
   m.put(t1, "doLaundry");
   m.put(t2, "payBills");
   m.put(t3, "cleanAttic");
   System.out.println(m.size());
} }

class ToDos{

  String day;

  ToDos(String d) { day = d; }

  public boolean equals(Object o) {
      return ((ToDos)o).day == this.day;
 }

// public int hashCode() { return 9; }
}

When // public int hashCode() { return 9; }is uncommented m.size()returns 2, when it's left commented it returns three. Why?

// public int hashCode() { return 9; }是取消注释 m.size()时,它的左评论回报2,它返回三个。为什么?

回答by David M

You have overidden equalswithout overriding hashCode. You must ensure that for all cases where equalsreturns true for two objects, hashCodereturns the same value. The hash code is a code that must be equal if two objects are equal (the converse need not be true). When you put your hard-coded value of 9 in, you satisfy the contract again.

您已覆盖equals但未覆盖hashCode. 您必须确保equals对于两个对象返回 true 的所有情况,hashCode返回相同的值。如果两个对象相等,则哈希码是必须相等的代码(反之不一定为真)。当您输入硬编码值 9 时,您再次满足合同。

In your hash map, equality is only tested within a hash bucket. Your two Monday objects should be equal, but because they are returning different hash codes, the equalsmethod isn't even called to determine their equality - they are put straight into different buckets, and the possibility that they are equal isn't even considered.

在您的哈希映射中,相等性仅在哈希桶内进行测试。你的两个 Monday 对象应该是相等的,但是因为它们返回不同的哈希码,所以equals甚至没有调用该方法来确定它们的相等性——它们被直接放入不同的桶中,甚至没有考虑它们相等的可能性。

回答by Avi

When you don't override the hashCode() method, your ToDos class inherits the default hashCode() method from Object, which gives every object a distinct hash code. This means that t1and t2have two different hash codes, even though were you to compare them, they would be equal. Depending on the particular hashmap implementation, the map is free to store them separately (and this is in fact what happens).

当您不覆盖 hashCode() 方法时,您的 ToDos 类会从 Object 继承默认的 hashCode() 方法,它为每个对象提供一个不同的哈希码。这意味着,t1t2有两个不同的散列码,即使是你对它们进行比较,他们是平等的。根据特定的 hashmap 实现,映射可以自由地单独存储它们(这实际上是发生的情况)。

When you do correctly override the hashCode() method to ensure that equal objects get equal hash codes, the hashmap is able to find the two equal objects and place them in the same hash bucket.

当您正确覆盖 hashCode() 方法以确保相等的对象获得相等的哈希码时,hashmap 能够找到两个相等的对象并将它们放在同一个哈希桶中。

A better implementation would give objects that are notequal differenthash codes, like this:

一个更好的实现会给等于不同哈希码的对象,像这样:

public int hashCode() {
    return (day != null) ? day.hashCode() : 0;
}

回答by allyourcode

When hashCode is uncommented, HashMap sees t1 and t2 as being the same thing; thus, t2's value clobbers that of t1. To understand how this works, note that when hashCode returns the same thing for two instances, they end up going to the same HashMap bucket. When you try to insert a second thing into the same bucket (in this case t2 is being inserted when t1 is already present), HashMap scans the bucket for another key that is equals. In your case, t1 and t2 are equals because they have the same day. At that point, "payBills" clobbers "doLaundry". As for whether t2 clobbers t1 as the key, I believe this is undefined; thus, either behavior is allowed.

当 hashCode 被取消注释时,HashMap 认为 t1 和 t2 是同一个东西;因此,t2 的值会破坏 t1 的值。要了解这是如何工作的,请注意当 hashCode 为两个实例返回相同的内容时,它们最终会进入同一个 HashMap 存储桶。当您尝试将第二个东西插入同一个存储桶时(在这种情况下,当 t1 已经存在时插入 t2),HashMap 会扫描存储桶以查找另一个等于的键。在您的情况下, t1 和 t2 是相等的,因为它们是同一天。那时,“payBills”打败了“doLaundry”。至于t2 clobbers t1 是否为key,我认为这是未定义的;因此,任何一种行为都是允许的。

There are a few important things to think about here:

这里有一些重要的事情需要考虑:

  1. Are two ToDos instances really equal just because they have the same day of the week?
  2. Whenever you implement equals, you should implement hashCode so that any two objects that are equals also have the same hashCode values. This is a fundamental assumption that HashMap makes. This is probably also true of anything else that relies the hashCode method.
  3. Design your hashCode method so that the hash codes are evenly distributed; otherwise, you won't get the performance benefits of hashing. From this perspective, returning 9 is one of the worst things you can do.
  1. 两个 ToDos 实例是否真的仅仅因为它们在一周中的同一天就相等?
  2. 每当你实现 equals 时,你应该实现 hashCode 以便任何两个相等的对象也具有相同的 hashCode 值。这是 HashMap 做出的一个基本假设。对于依赖 hashCode 方法的任何其他事物,这可能也是如此。
  3. 设计你的 hashCode 方法,使哈希码均匀分布;否则,您将无法获得散列的性能优势。从这个角度来看,返回 9 是你能做的最糟糕的事情之一。

回答by Tom

I cannot emphasize enough that you should read Chapter 3 in Effective Java(warning: pdf link). In that chapter you will learn everything you need to know about overriding methods in Object, and in particular, about the equalscontract. Josh Bloch has a great recipe for overriding the equalsmethod that you should follow. And it will help you understand why you should be using equalsand not ==in your particular implementation of the equalsmethod.

我怎么强调你都应该阅读Effective Java 中的第 3 章(警告:pdf 链接)。在那一章中,您将学习关于 中的覆盖方法Object,特别是equals契约的所有您需要了解的知识。Josh Bloch 有一个很好的方法来覆盖equals你应该遵循的方法。它会帮助你理解为什么你应该使用equals而不是==在你的equals方法的特定实现中。

Hope this helps. PLEASE READ IT. (At least the first couple items... and then you will want to read the rest :-).

希望这可以帮助。请阅读。(至少是前几项......然后你会想要阅读其余的:-)。

-Tom

-汤姆

回答by Shadab Khan

when you comment, it returns 3;

当你发表评论时,它返回 3;

because hashCode() inherited from the Object is ONLY called which returns 3 different hashcodes for the 3 ToDos objects. The unequal hashcodes means the 3 objects are destined to different buckets and equals() return false as they are the first entrant in their respective buckets. If the hashCodes are different it is understood in advance that the objects are unequal. They will go in different buckets.

因为从 Object 继承的 hashCode() 仅被调用,它为 3 个 ToDos 对象返回 3 个不同的哈希码。不相等的哈希码意味着这 3 个对象被指定到不同的存储桶,并且 equals() 返回 false,因为它们是各自存储桶中的第一个进入者。如果 hashCode 不同,则事先可以理解对象是不相等的。他们将进入不同的桶。

when you uncomment, it returns 2;

当你取消注释时,它返回 2;

because here the overridden hashCode() is called which returns the same value for all the ToDos and they all will have to go into one bucket, connected linearly. Equal hashcodes dont promise anything about the equality or inequality of objects.

因为这里调用了覆盖的 hashCode() ,它为所有 ToDo 返回相同的值,并且它们都必须进入一个桶,线性连接。相等的哈希码不保证对象的相等或不相等。

hashCode() for t3 is 9 and as it is the first entrant, equals() is false and t3 inserted in the bucket- say bucket0.

t3 的 hashCode() 是 9,因为它是第一个进入者,所以 equals() 是假的,t3 被插入到桶中——比如 bucket0。

Then t2 getting the same hashCode() as 9 is destined for the same bucket0, a subsequent equals() on the already residing t3 in bucket0 returns false by the definition of overridden equal().

然后 t2 获得与 9 相同的 hashCode() 指向同一个bucket0,随后在bucket0 中已经驻留的t3 上的equals() 根据重写的equal() 的定义返回false。

Now t1 with hashCode() as 9 is also destined for bucket0, and a subsequent equals() call returns true when compared with the pre-existing t2 in the same bucket. t1 fails to enter the map. So the net size of map is 2 -> {ToDos@9=cleanAttic, ToDos@9=payBills}

现在,hashCode() 为 9 的 t1 也指向 bucket0,并且与同一存储桶中预先存在的 t2 相比,随后的 equals() 调用返回 true。t1 无法进入地图。所以地图的净大小是 2 -> {ToDos@9=cleanAttic, ToDos@9=payBills}

This explains the importance of implementing both equals() and hashCode(), and in such a way that the fields taken up in determining equals() must also be taken when determining hashCode(). This will guarantee that if two objects are equal they will always have same hashCodes. hashCodes should not be perceived as pseudo-random numbers as they must be consistent with equals()

这解释了同时实现 equals() 和 hashCode() 的重要性,并且在确定 hashCode() 时也必须采用确定 equals() 时使用的字段。这将保证如果两个对象相等,它们将始终具有相同的 hashCode。hashCode 不应被视为伪随机数,因为它们必须与 equals() 一致

回答by supercat

Rather than thinking of hashCodein terms of hash-bucket mapping, I think it's more helpful to think somewhat more abstractly: an observation that two objects have different hash codes constitutes an observation that the objects are not equal. As a consequence of that, an observation that none of the objects in a collection have a particular hash code constitutes an observation that none of the objects in a collection are equal to any object which has that hash code. Further, an observation that none of the objects in a collection have a hash code with some trait constitutes an observation that none of them are equal to any object which does.

与其hashCode从哈希桶映射的角度考虑,我认为更抽象地思考更有帮助:两个对象具有不同哈希代码的观察构成了对象不相等的观察。因此,如果观察到集合中没有任何对象具有特定的散列码,就构成了一个观察,即集合中的任何对象都不等于具有该散列码的任何对象。此外,观察到集合中的任何对象都没有具有某些特征的哈希码,这构成了观察,即它们都不等于任何具有特征的对象。

Hash tables generally work by defining a family of traits, exactly one of of which will be applicable to each object's hash code (e.g. "being congruent to 0 mod 47", "being congruent to 1 mod 47", etc.), and then having a collection of objects with each trait. If one is then given an object and can determine which trait applies to it, one can know that it must be in a collection of things with that trait.

哈希表通常通过定义一系列特征来工作,其中一个特征将适用于每个对象的哈希码(例如“与 0 mod 47 一致”、“与 1 mod 47 一致”等),然后拥有具有每个特征的对象集合。如果然后给一个对象并且可以确定哪个特征适用于它,则可以知道它必须在具有该特征的事物集合中。

That hash tables generally use a sequence of numbered buckets is an implementation detail; what is essential is that an object's hash code is quickly used to identify many things which it cannot possibly be equal to, and with which it thus will not have to be compared.

哈希表通常使用一系列编号的桶是一个实现细节;重要的是,一个对象的散列码可以快速用于识别许多它不可能相等的事物,因此不必与它们进行比较。

回答by Paolo Maresca

According to Effective Java,

根据 Effective Java 的说法,

Always override hashCode() when you override equals()

覆盖 equals() 时始终覆盖 hashCode()

well, why? Simple, because different objects (content, not references) should get different hash codes; on the other hand, equal objects should get the same hash code.

好吧,为什么?很简单,因为不同的对象(内容,而不是引用)应该得到不同的哈希码;另一方面,相等的对象应该获得相同的哈希码。

According to above, Java associative data structures compare the results obtained by equals() and hashCode() invokations to create the buckets. If both are the same, objects are equals; otherwise not.

根据上文,Java 关联数据结构比较了 equals() 和 hashCode() 调用获得的结果以创建桶。如果两者相同,则对象相等;否则不会。

In the specific case (i.e. the one presented above), when hashCode() is commented, a random number is generated for each instance (behaviour inherited by Object) as hash, the equals() checks String's references (remember Java String Pool), so the equals() should return truebut the hashCode() not, the result is 3 different objects stored. Let's see what happens in case the hashCode() respecting the contract but returning always 9 is uncommented. Well, hashCode() is constantly the same, the equals() returns truefor the two Strings in the Pool (i.e. "Monday"), and for them the bucket will be the same resulting in only 2 elements stored.

在特定情况下(即上面介绍的那个),当 hashCode() 被注释时,为每个实例(由 Object 继承的行为)生成一个随机数作为哈希,equals() 检查 String 的引用(记住 Java String Pool),所以 equals() 应该返回true但 hashCode() 不是,结果是存储了 3 个不同的对象。让我们看看如果 hashCode() 遵守合约但始终返回 9 未注释会发生什么。嗯,hashCode() 总是相同的,对于池中的两个字符串(即“星期一”),equals() 返回true,并且对于它们,存储桶将相同,导致仅存储 2 个元素

Therefore, it's definitely needed to be careful in using the hashCode() and equals() overriding, in particular when compound data types are user defined and they are used with Java associative data structures.

因此,在使用 hashCode() 和 equals() 覆盖时绝对需要小心,特别是当复合数据类型是用户定义的并且它们与 Java 关联数据结构一起使用时。

回答by Hiren

Whenever you create a new object in Java, it will be assigned a unique hashcode by JVM itself. If you wouldn't override hashcode method then object will get unique hascode and hence a unique bucket (Imagine bucket is nothing but a place in memory where JVM will go to find an object).

每当您在 Java 中创建一个新对象时,JVM 都会为其分配一个唯一的哈希码。如果您不覆盖 hashcode 方法,则对象将获得唯一的哈希码,因此将获得唯一的存储桶(想象一下,存储桶只不过是内存中的一个地方,JVM 将在其中查找对象)。

(you can check uniqueness of an hashcode by calling hashcode method on each object and printing their values on console)

(您可以通过在每个对象上调用 hashcode 方法并在控制台上打印它们的值来检查哈希码的唯一性)

In your case when you are un commentting hashcode method, hashmap firstly look for bucket having same hashcode that method returns. And everytime you are returning same hashcode. Now when hashmap finds that bucket, it will compare current object with the object residing into bucket using euqals method. Here it finds "Monday" and so hashmap implementation do not allow to add it again because there is already an object having same hashcode and same euqality implementation.

在您取消注释 hashcode 方法的情况下,hashmap 首先查找具有该方法返回的相同哈希码的存储桶。每次您返回相同的哈希码时。现在,当 hashmap 找到该桶时,它将使用 euqals 方法将当前对象与驻留在桶中的对象进行比较。在这里它找到“星期一”,因此 hashmap 实现不允许再次添加它,因为已经有一个对象具有相同的哈希码和相同的 euqality 实现。

When you comment hashcode method, JVM simply returns different hashcode for all the three objects and hence it never even bother about comapring objects using equals method. And so there will be three different objects in Map added by hashmap implementation.

当您注释 hashcode 方法时,JVM 只是为所有三个对象返回不同的 hashcode,因此它甚至从不关心使用 equals 方法对对象进行 comapring。因此,在 Map 中将通过 hashmap 实现添加三个不同的对象。

回答by hidro

HashMapuses hashCode(), ==and equals()for entry lookup. The lookup sequence for a given key kis as follows:

HashMap使用hashCode(),==equals()用于条目查找。给定键的查找顺序k如下:

  • Use k.hashCode()to determine which bucket the entry is stored, if any
  • If found, for each entry's key k1in that bucket, if k == k1 || k.equals(k1), then return k1's entry
  • Any other outcomes, no corresponding entry
  • 使用k.hashCode()以确定哪个桶条目存储,如果有的话
  • 如果找到,对于k1该桶中每个条目的键,如果k == k1 || k.equals(k1),则返回k1的条目
  • 任何其他结果,没有相应的条目

To demonstrate using an example, assume that we want to create a HashMapwhere keys are something which is 'logically equivalent' if they have same integer value, represented by AmbiguousIntegerclass. We then construct a HashMap, put in one entry, then attempt to override its value and retrieve value by key.

为了使用示例进行演示,假设我们要创建一个HashMapwhere 键是“逻辑等效”的东西,如果它们具有相同的整数值,由AmbiguousInteger类表示。然后我们构造一个HashMap,放入一个条目,然后尝试覆盖它的值并通过键检索值。

class AmbiguousInteger {
    private final int value;

    AmbiguousInteger(int value) {
        this.value = value;
    }
}

HashMap<AmbiguousInteger, Integer> map = new HashMap<>();
// logically equivalent keys
AmbiguousInteger key1 = new AmbiguousInteger(1),
                 key2 = new AmbiguousInteger(1),
                 key3 = new AmbiguousInteger(1);
map.put(key1, 1); // put in value for entry '1'
map.put(key2, 2); // attempt to override value for entry '1'
System.out.println(map.get(key1));
System.out.println(map.get(key2));
System.out.println(map.get(key3));

Expected: 2, 2, 2

Don't override hashCode()and equals(): by default Java generates different hashCode()values for different objects, so HashMapuses these values to map key1and key2into different buckets. key3has no corresponding bucket so it has no value.

不要覆盖hashCode()andequals():默认情况下,Java 会hashCode()为不同的对象生成不同的值,因此HashMap使用这些值来映射key1key2放入不同的存储桶中。key3没有对应的桶,所以它没有价值。

class AmbiguousInteger {
    private final int value;

    AmbiguousInteger(int value) {
        this.value = value;
    }
}

map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 2, set as entry 2[1]
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 2, get as entry 2[1]
map.get(key3); // map to no bucket
Expected: 2, 2, 2
Output:   1, 2, null

Override hashCode()only:HashMapmaps key1and key2into the same bucket, but they remain different entries due to both key1 == key2and key1.equals(key2)checks fail, as by default equals()uses ==check, and they refer to different instances. key3fails both ==and equals()checks against key1and key2and thus has no corresponding value.

hashCode()仅覆盖:HashMap映射key1key2进入同一个存储桶,但由于两者key1 == key2key1.equals(key2)检查失败,它们保持不同的条目,因为默认情况下equals()使用==检查,并且它们引用不同的实例。key3两者都失败==equals()检查key1key2,因此没有相应的值。

class AmbiguousInteger {
    private final int value;

    AmbiguousInteger(int value) {
        this.value = value;
    }

    @Override
    public int hashCode() {
        return value;
    }
}

map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 1, set as entry 1[2]
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 1, get as entry 1[2]
map.get(key3); // map to bucket 1, no corresponding entry
Expected: 2, 2, 2
Output:   1, 2, null

Override equals()only:HashMapmaps all keys into different buckets because of default different hashCode(). ==or equals()check is irrelevant here as HashMapnever reaches the point where it needs to use them.

equals()仅覆盖:HashMap由于默认不同,将所有键映射到不同的存储桶中hashCode()==equals()check 在这里无关紧要,因为HashMap永远不会到达需要使用它们的地步。

class AmbiguousInteger {
    private final int value;

    AmbiguousInteger(int value) {
        this.value = value;
    }

    @Override
    public boolean equals(Object obj) {
        return obj instanceof AmbiguousInteger && value == ((AmbiguousInteger) obj).value;
    }
}

map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 2, set as entry 2[1]
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 2, get as entry 2[1]
map.get(key3); // map to no bucket
Expected: 2, 2, 2
Actual:   1, 2, null

Override both hashCode()and equals(): HashMapmaps key1, key2and key3into the same bucket. ==checks fail when comparing different instances, but equals()checks pass as they all have the same value, and deemed 'logically equivalent' by our logic.

同时覆盖hashCode()equals()HashMap地图key1key2key3进入同一个桶。==比较不同实例时检查失败,但equals()检查通过,因为它们都具有相同的值,并且被我们的逻辑视为“逻辑等效”。

class AmbiguousInteger {
    private final int value;

    AmbiguousInteger(int value) {
        this.value = value;
    }

    @Override
    public int hashCode() {
        return value;
    }

    @Override
    public boolean equals(Object obj) {
        return obj instanceof AmbiguousInteger && value == ((AmbiguousInteger) obj).value;
    }
}

map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 1, set as entry 1[1], override value
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 1, get as entry 1[1]
map.get(key3); // map to bucket 1, get as entry 1[1]
Expected: 2, 2, 2
Actual:   2, 2, 2

What if hashCode()is random?: HashMapwill assign a different bucket for each operation, and thus you never find the same entry that you put in earlier.

如果hashCode()是随机的呢?:HashMap将为每个操作分配不同的存储桶,因此您永远不会找到之前放入的相同条目。

class AmbiguousInteger {
    private static int staticInt;
    private final int value;

    AmbiguousInteger(int value) {
        this.value = value;
    }

    @Override
    public int hashCode() {
        return ++staticInt; // every subsequent call gets different value
    }

    @Override
    public boolean equals(Object obj) {
        return obj instanceof AmbiguousInteger && value == ((AmbiguousInteger) obj).value;
    }
}

map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 2, set as entry 2[1]
map.get(key1); // map to no bucket, no corresponding value
map.get(key2); // map to no bucket, no corresponding value
map.get(key3); // map to no bucket, no corresponding value
Expected: 2, 2, 2
Actual:   null, null, null

What if hashCode()is always the same?: HashMapmaps all keys into one big bucket. In this case, your code is functionally correct, but the use of HashMapis practically redundant, as any retrieval would need to iterate through all entries in that single bucket in O(N) time (or O(logN) for Java 8), equivalent to the use of a List.

如果hashCode()总是一样呢?:HashMap将所有键映射到一个大桶中。在这种情况下,您的代码在功能上是正确的,但HashMap实际上是多余的,因为任何检索都需要在 O(N) 时间(或 Java 8 的 O(logN))内迭代该单个存储桶中的所有条目,等效使用一个List.

class AmbiguousInteger {
    private final int value;

    AmbiguousInteger(int value) {
        this.value = value;
    }

    @Override
    public int hashCode() {
        return 0;
    }

    @Override
    public boolean equals(Object obj) {
        return obj instanceof AmbiguousInteger && value == ((AmbiguousInteger) obj).value;
    }
}

map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 1, set as entry 1[1]
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 1, get as entry 1[1]
map.get(key3); // map to bucket 1, get as entry 1[1]
Expected: 2, 2, 2
Actual:   2, 2, 2

And what if equalsis always false?: ==check passes when we compare the same instance with itself, but fails otherwise, equalschecks always fails so key1, key2and key3are deemed to be 'logically different', and maps to different entries, though they are still in the same bucket due to same hashCode().

如果equals总是假的怎么办?==检查,当我们与自身比较相同情况下通过,但在其他方面没有,equals检查总是失败如此key1key2并且key3被认为是“逻辑上不同”,并映射到不同的条目,但他们仍然在同一个桶中,由于相同的hashCode()

class AmbiguousInteger {
    private final int value;

    AmbiguousInteger(int value) {
        this.value = value;
    }

    @Override
    public int hashCode() {
        return 0;
    }

    @Override
    public boolean equals(Object obj) {
        return false;
    }
}

map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 1, set as entry 1[2]
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 1, get as entry 1[2]
map.get(key3); // map to bucket 1, no corresponding entry
Expected: 2, 2, 2
Actual:   1, 2, null

Okay what if equalsis always true now?: you're basically saying that all objects are deemed 'logically equivalent' to another, so they all map to the same bucket (due to same hashCode()), same entry.

好吧,如果equals现在总是真的呢?:您基本上是说所有对象都被视为与另一个对象“逻辑等效”,因此它们都映射到同一个存储桶(由于相同hashCode())、相同的条目。

class AmbiguousInteger {
    private final int value;

    AmbiguousInteger(int value) {
        this.value = value;
    }

    @Override
    public int hashCode() {
        return 0;
    }

    @Override
    public boolean equals(Object obj) {
        return true;
    }
}

map.put(key1, 1); // map to bucket 1, set as entry 1[1]
map.put(key2, 2); // map to bucket 1, set as entry 1[1], override value
map.put(new AmbiguousInteger(100), 100); // map to bucket 1, set as entry1[1], override value
map.get(key1); // map to bucket 1, get as entry 1[1]
map.get(key2); // map to bucket 1, get as entry 1[1]
map.get(key3); // map to bucket 1, get as entry 1[1]
Expected: 2, 2, 2
Actual:   100, 100, 100