Java中的Map与Integer键:键的比较方式是什么?

7

我只是想要确保使用Integer对象作为键时,我的代码是安全的。这里有一个简短的示例:

Integer int1 = new Integer(1337);
Integer int2 = new Integer(1337);

if (int1 == int2) {
    System.out.println("true");
} else {
    System.out.println("false");
}

if (int1.equals(int2)) {
    System.out.println("true");
} else {
    System.out.println("false");
}

Map<Integer, Object> map = new HashMap<Integer, Object>();
map.put(int1, null);
map.put(int2, null);

System.out.println(map.size());

代码将会输出:
false
true
1

我之前就有这个预期,引用可能不同,但它们是相等的。现在我对 Map 的行为很感兴趣。

  • 是否保证像 Map 或 Set 这类的集合会通过内容比较键而非引用比较键?
  • 还是这取决于实际的实现,比如 HashMap?

1
@close: 这不是重复问题 - 其他相关帖子处理的是“equals vs. ==”的问题,而这个问题是关于Integer作为集合标识符的行为如何!这就是为什么底部有项目符号。我在问:集合如何处理它。 - Scolytus
8个回答

8
调用equals方法,因此比较的是内容。
关于您上面的两个问题:
给定两个对象o1o2(为简化起见,我们假设o1!= nullo2!= null),哈希映射最终必须确定它们是否具有相同的值。(HashMap还检查o1o2是否具有相同的哈希值,但在您的问题的上下文中,这不重要)。它通过调用方法equals()来实现。只要o1.equals(o2)为false,HashMap就认为两个对象是两个不同的键。 HashSet也调用equals()方法来确定元素是否已经包含在集合中,请参见http://docs.oracle.com/javase/6/docs/api/java/util/HashSet.html#add%28E%29
另一方面,TreeMap必须比较这两个对象,并确定它们是否相等,或者哪一个更大。它通过调用compareTo()来实现。因此,o1.compareTo(o2)的返回值很重要(或者,如果您使用构造函数http://docs.oracle.com/javase/6/docs/api/java/util/TreeMap.html#TreeMap%28java.util.Comparator%29创建了树映射,则使用比较器)。
因此可以保证,在HashMapHashSet中使用equals()方法来区分对象,在TreeMap中使用compareTo()方法。其他地图实现可能使用==(例如IdentityHashMap)。

实际上这取决于equals方法的实现。无论如何,您都可以在equals方法中通过引用进行比较。 - nhahtdh
哈希码(hashcode)方法怎么办呢?尽管equals()返回true,但如果哈希码(hashcode)的值不同,这两个键仍然相同。 - Vallabh Patade
1
@VallabhPatade:hashCode的契约:如果两个对象根据equals(Object)方法是相等的,那么在这两个对象的hashCode方法上调用必须产生相同的整数结果。你可以违反契约,但依赖于它的应用程序将会中断。 - nhahtdh
你的意思是如果两个对象是“equals()”相等的,那么无论“hashcode()”返回什么值,在哈希映射中这两个对象都是相同的键。 - Vallabh Patade
你是第一个回答的,基本上你的答案是正确的。你能否更详细地解释一下我提出的两个问题(底部的项目符号)?然后我会很乐意接受你的答案。 - Scolytus

6

问题1:- Map或Set等集合是否保证按内容而不是引用比较键?

回答1:- 不是的。Collection、Map和Set是接口,它们只保证可能方法的契约。

问题2:- 这取决于实际的实现,例如HashMap吗?

回答2:- 是的。如何处理比较是开发人员的决定。

HashMap使用两个东西来分配它们的对象

第一个是Object#hashCode(),用于计算索引。

第二个是Object#equals(),用于处理哈希冲突。


3
如果您打开java.util.AbstractMap实现,您会发现在所有地方都使用Object#equals方法检查键和值的等式。实际上,在地图内部将比较什么取决于Object#equals方法的键/值实现。

1

这里的Integer是一个Waraper最终类,它重写了equals()方法,所以它只会比较内容。

因此,如果使用Integers作为任何包装类,在Map中就没有问题。

假设您想要使用自定义类作为键,您需要重写equals()和hashcode()方法,以避免在Map中出现重复。


1
不是普遍的真理,你可以很容易地拥有一个最终类来覆盖 equals() 方法,仍然测试对象的相等性而不是内容的相等性... - Anders R. Bystrup
Integer类是一个final类,但它仅仅比较内容。这意味着我们无法重写Integer类的方法来改变其行为。 - NPKR
也许你应该提到,通常情况下,Maps调用equals方法来确定键的相等性 - 这就是我所问的 ;) - Scolytus

0

当将一个项目放入HashMap(以及HashSet)中时,使用hashCode(通过简单的线性函数转换)来确定该项应放置在其内部集合中的位置。

然后,在确定的位置上,它搜索相同的对象(在存储的所有项目中)使用(o1 == o2 || o1.equals(o2)),如果引用不同,则调用#equals函数,这是对简单#equals调用的性能改进。如果找到相同的项目,则其分配的值将替换为新值,如果没有,则新项目将简单地添加到内部集合中。


0

这实际上取决于您为Map指定的K/key的equals()实现。

尝试使用Map<Object,String>进行相同操作,看看会发生什么(提示:要使Object相等,它们实际上必须是相同的对象)。

干杯!


0

第一个比较两个不同的对象(引用)--> false。

第二个比较这些对象的值(相等)--> true

Hashmap使用对象的equals和hascode方法来确定唯一键。因此,如果您插入相同的键两次,则会导致一个剩余元素。第二个元素。查看Map#put javadoc以了解发生了什么。


这不完全是我所问的,但Javadoc给了我一个提示。重要的部分在.containsKey()中,其中指定使用.equals()。 - Scolytus

0
在哈希映射中,键使用equals()和hashcode()方法进行比较。
在上面的示例中,Integer类中重写了hashcode()和equals()方法。当HashMap比较两个键时,首先它会获取该对象的hashcode(),然后在具有相同hashcode值的键上调用equals()方法。

这并不是我问题的确切答案。 - Scolytus
这不是我问题的答案。但似乎我的问题不太容易理解。至少人们没有理解重点。我主要关注问题末尾的两个要点,几乎没有人回答。无论如何,感谢所有答案的总和,我已经弄清楚了。所有地图都应该调用equals()进行键比较。也许我会编辑问题来整理一下,我认为更多的上下文会更好... - Scolytus

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