我有这个测试代码:
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; }
}
取消注释// public int hashCode() { return 9; }
时m.size()
返回2,当它被注释时,它会返回3。为什么呢?
答案 0 :(得分:61)
HashMap
使用hashCode()
,==
和equals()
进行条目查找。给定键k
的查找序列如下:
k.hashCode()
确定条目存储的存储区(如果有)k1
,如果k == k1 || k.equals(k1)
,则返回k1
的条目为了演示使用示例,假设我们要创建一个HashMap
,其中如果键具有相同的整数值(由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
不要覆盖hashCode()
和equals()
:默认情况下,Java为不同的对象生成不同的hashCode()
值,因此HashMap
使用这些值将key1
和key2
映射到不同的存储区。 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
仅覆盖hashCode()
: HashMap
将key1
和key2
映射到同一个存储分区,但由于{{{}} key1 == key2
和key1.equals(key2)
1}}和equals()
检查失败,因为默认情况下==
使用key3
检查,并且它们引用不同的实例。 ==
对equals()
和key1
进行key2
和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
次检查失败,因此没有相应的值。
equals()
仅覆盖HashMap
: hashCode()
将所有密钥映射到不同的存储区,因为默认值不同==
。 equals()
或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
永远无法达到需要使用它们的程度。
hashCode()
覆盖equals()
和HashMap
:key1
地图key2
,key3
和==
桶。 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
检查通过,因为它们都具有相同的值,并且被我们的逻辑视为“逻辑上等效”。
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
会为每个操作分配一个不同的存储桶,因此您永远不会找到您之前输入的相同条目。 / p>
hashCode()
如果HashMap
始终相同,该怎么办?:HashMap
将所有密钥映射到一个大桶中。在这种情况下,您的代码在功能上是正确的,但List
的使用实际上是多余的,因为任何检索都需要在O(N)时间(or O(logN) for Java 8)内迭代该单个存储桶中的所有条目,相当于使用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
。
equals
如果==
始终为假,该怎么办?:equals
当我们将同一个实例与自身进行比较时检查通过,但否则失败,key1
始终检查失败,因此key2
,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
它们仍然在同一个存储桶中
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
),相同的条目。
def Area_Of_A_Rectangle():
print("To Find the Area of a Rectangle we need two things")
print("The Area of a Rectangle: ",Rectangle_Height() * Rectangle_Width() )
##!--------------------Validation Modules------------------!##
#-----Rectangle Height Input------------------------------------------------
def Rectangle_Height():
try:
Rec_Height = float(input("What is the Height of your Rectangle: "))
except ValueError:
print("Please Try Again!")
Rectangle_Height()
else:
return Rec_Height
#-----Rectangle Width-------------------------------------------------------
def Rectangle_Width():
try:
Rec_Width = float(input("What is the Width of your Rectangle: "))
except ValueError:
print("Please Try Again!")
Rectangle_Width()
else:
return Rec_Width
Area_Of_A_Rectangle()
答案 1 :(得分:44)
您已覆盖equals
而未覆盖hashCode
。对于两个对象equals
返回true的所有情况,您必须确保hashCode
返回相同的值。哈希码是一个代码,如果两个对象相等则必须相等(反之不必为真)。当您将硬编码值设置为9时,您再次满足合同。
在哈希映射中,仅在哈希桶中测试相等性。你的两个星期一对象应该是相同的,但是因为它们返回不同的哈希码,所以甚至不调用equals
方法来确定它们的相等性 - 它们被直接放入不同的桶中,并且它们相等的可能性不是甚至考虑过。
答案 2 :(得分:8)
我无法强调你应该阅读Chapter 3 in Effective Java(警告:pdf链接)。在本章中,您将了解有关Object
中覆盖方法的所有信息,尤其是equals
合同。 Josh Bloch有一个很好的方法来覆盖你应该遵循的equals
方法。它将帮助您理解为什么在equals
方法的特定实现中使用==
而不是equals
。
希望这会有所帮助。请仔细阅读。 (至少前几个项目......然后你会想要阅读其余部分: - )。
- 汤姆
答案 3 :(得分:6)
当您不覆盖hashCode()方法时,您的ToDos类从Object继承默认的hashCode()方法,该方法为每个对象提供不同的哈希代码。这意味着t1
和t2
有两个不同的哈希码,即使您要比较它们,它们也是相同的。根据特定的hashmap实现,地图可以单独存储它们(这实际上就是这样)。
当你正确覆盖hashCode()方法以确保相等的对象获得相同的哈希码时,hashmap能够找到两个相等的对象并将它们放在同一个哈希桶中。
更好的实现会为不等于不同哈希码的对象提供如下:
public int hashCode() {
return (day != null) ? day.hashCode() : 0;
}
答案 4 :(得分:4)
当你发表评论时,它会返回3;
因为仅从Object调用的hashCode()被调用,它返回3个ToDos对象的3个不同的哈希码。不相等的哈希码意味着3个对象指向不同的桶,而equals()返回false,因为它们是各自桶中的第一个进入者。 如果hashCodes不同,则事先可以理解对象是不相等的。 他们将使用不同的桶。
取消注释时,返回2;
因为这里调用了被覆盖的hashCode(),它为所有ToDos返回相同的值,并且它们都必须进入一个桶,线性连接。 相等的哈希码不承诺任何关于对象的相等或不等的东西。
t3的hashCode()是9,因为它是第一个进入者,equals()为false,t3插入桶中 - 比如bucket0。然后t2获得与9相同的hashCode()指向同一个bucket0,在bucket0中已经驻留的t3上的后续equals()通过重写的equal()的定义返回false。
现在,带有hashCode()为9的t1也指向bucket0,并且当与同一存储桶中的预先存在的t2进行比较时,后续的equals()调用返回true。 t1无法进入地图。 所以地图的净大小是2 - > {ToDos @ 9 = cleanAttic,ToDos @ 9 = payBills}
这解释了实现equals()和hashCode()的重要性,并且在确定hashCode()时也必须采用确定equals()时所采用的字段。 这将保证如果两个对象相等,它们将始终具有相同的hashCodes。 hashCodes不应被视为伪随机数,因为它们必须与equals()
一致答案 5 :(得分:3)
根据Effective Java,
好吧,为什么?很简单,因为不同的对象(内容,而不是引用)应该得到不同的哈希码;另一方面,相等的对象应该获得相同的哈希码。覆盖equals()
时,始终覆盖hashCode()
根据以上所述,Java关联数据结构比较equals()和hashCode()invokations获得的结果来创建存储桶。如果两者相同,则对象等于;否则没有。
在特定情况下(即上面提到的那个),当hashCode()评论时,为每个实例(Object继承的行为)生成一个随机数作为hash,equals()检查String的引用(记住Java String Pool),所以equals()应返回 true 但hashCode()不返回,结果是 3个不同的对象存储。 让我们看看在hashCode()尊重合同但总是返回9的情况下取消注释会发生什么。好吧,hashCode()总是一样的,equals()为池中的两个字符串返回 true (即“星期一”),对于它们,桶将是相同的结果只存储了2个元素。
因此,使用hashCode()和equals()重写时必须要小心,特别是当复合数据类型是用户定义的并且它们与Java关联数据结构一起使用时。
答案 6 :(得分:0)
当取消注释hashCode时,HashMap将t1和t2看作同一个东西;因此,t2的价值是t1的价值。要了解其工作原理,请注意,当hashCode为两个实例返回相同的内容时,它们最终会转到相同的HashMap存储桶。当您尝试将第二个内容插入同一个存储桶时(在这种情况下,当t1已经存在时插入了t2),HashMap会扫描存储桶以获取另一个等于的键。在您的情况下,t1和t2是相等的,因为它们具有相同的一天。那时,“payBills”蠢货“doLaundry”。至于t2 clobbers t1是否为关键,我相信这是不确定的;因此,任何一种行为都是允许的。
这里有一些重要的事情需要考虑:
答案 7 :(得分:0)
我认为在哈希桶映射方面不考虑hashCode
,而是更抽象地思考更有帮助:观察两个对象具有不同的哈希码构成观察对象不相等。因此,观察到集合中的任何对象都没有特定的哈希码构成观察,即集合中的任何对象都不等于具有该哈希码的任何对象。此外,观察到集合中的任何对象都没有具有某种特征的哈希码,这构成了一种观察结果,即它们都不等于任何对象。
哈希表通常通过定义一系列特征来工作,其中一个特征将适用于每个对象的哈希码(例如“与0 mod 47一致”,“与1 mod 47一致”等) ,然后拥有每个特征的对象集合。如果给一个对象并且可以确定哪个特征适用于它,那么可以知道它必须在具有该特征的事物的集合中。
哈希表通常使用一系列编号桶是一个实现细节;重要的是,对象的哈希码很快就会用来识别许多不可能与之相等的东西,因此不需要对它进行比较。
答案 8 :(得分:0)
无论何时在Java中创建新对象,JVM本身都会为其分配一个唯一的哈希码。如果你不会覆盖hashcode方法,那么object将获得唯一的hascode,因此一个唯一的存储桶(Imagine存储桶只是内存中JVM将用于查找对象的位置)。
(您可以通过在每个对象上调用hashcode方法并在控制台上打印它们来检查哈希码的唯一性)
在您不注释hashcode方法的情况下,hashmap首先查找具有该方法返回的相同哈希码的存储桶。每次返回相同的哈希码时。现在,当hashmap找到该存储桶时,它将使用euqals方法将当前对象与驻留在存储桶中的对象进行比较。在这里它找到&#34;星期一&#34;所以hashmap实现不允许再次添加它,因为已经有一个对象具有相同的哈希码和相同的euqality实现。
当您评论hashcode方法时,JVM只会为所有三个对象返回不同的哈希码,因此它甚至不会使用equals方法对comapring对象感到烦恼。因此,通过hashmap实现添加Map中将有三个不同的对象。