2009-01-15 100 views
16

此问题是由strange HashMap.put() behaviourLong.valueOf(0).equals(Integer.valueOf(0))为什么是false?

促使我想我明白为什么Map<K,V>.put需要KMap<K,V>.get需要一个Object,似乎不这样做会打破太多现有的代码。

现在我们进入一个非常容易出错的情景:

java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>(); 
m.put(5L,"Five"); // compiler barfs on m.put(5, "Five") 
m.contains(5); // no complains from compiler, but returns false 

无法这一已被如果Long值withing int范围和值相等返回true解决了吗?

回答

24

下面是从Long.java

public boolean equals(Object obj) { 
    if (obj instanceof Long) { 
     return value == ((Long)obj).longValue(); 
    } 
    return false; 
} 

即源它需要是Long类型才是平等的。我认为关键的区别:以上

long l = 42L 
int i = 42; 
l == i 

和您的例子是与原语int值的隐式扩大,可能会发生,但与对象类型有从整数隐式转换为龙没有规则。

也检查出Java Puzzlers,它有很多类似这样的例子。

+1

也许我不清楚。我知道为什么它会像源代码那样发生,我在发布前阅读过代码。我的问题是为什么它决定应该这样? – 2009-01-15 08:53:19

+3

http://stackoverflow.com/questions/445990/why-is-long-valueof0-equalsinteger-valueof0-false#446911更正确地解释它。由于将Long与Integer比较可能会违反对称性。 – 2009-01-15 20:44:04

5

是的,但这一切都归结于比较算法以及转换的距离。例如,当您尝试m.Contains("5")时,您希望发生什么?或者如果你传递一个5作为第一个元素的数组?简而言之,它似乎是连接起来的“如果类型不同,键是不同的”。

然后以object为关键集合。如果你put a 5L,你想要发生什么,然后尝试获得5,"5",...?如果put a 5L5"5"以及想要检查5F怎么办?由于它是一个通用集合(或模板化,或任何你想调用它),它必须检查并为某些值类型做一些特殊的比较。如果K为int,则检查对象是否通过longshort,float,double,...,然后进行转换和比较。如果K是float然后检查物体是否通过...

您明白了。

但是,如果类型不匹配,另一个实现可能是抛出异常,我经常希望它发生异常。

+1

我不明白这一点。当然,等于一个Inter的Long与一个等于Integer的String是有区别的吗?例如有隐式转换。 例外可能有助于发现这种情况,是的。 – 2009-01-15 08:57:27

4

你的问题看起来似乎是合理的,但是如果不是它的合约,它将违反equals()的一般约定来返回两种不同类型的真。

0

与C++不同,Java语言设计的一部分是针对对象永远不会隐式地转换为其他类型。这是使Java成为一种小而简单的语言的一部分。 C++复杂性的一个合理部分来自隐式转换及其与其他功能的交互。另外,Java在基元和对象之间有一个尖锐而明显的二分法。这与其他语言的不同之处在于,这种差异作为优化而隐藏在封面之下。这意味着你不能指望Long和Integer像long和int那样行事。

可以编写库代码来隐藏这些差异,但实际上可以通过使编程环境不太一致来造成伤害。

6

一般而言,虽然equals()合同中没有严格规定,但对象不应该认为自己与另一个不完全相同的对象(即使它是子类)相同。考虑对称性 - 如果a.equals(b)为真,则b.equals(a)也必须为真。

让我们两个对象,Superfoo,并Sub类,它扩展Superbar。现在考虑在Super中实现equals(),特别是当它被称为foo.equals(bar)时。 Foo只知道该栏强制类型为Object,因此为了获得准确的比较,需要检查它是否为Super的实例,如果不返回false。这是,所以这部分是好的。它现在比较所有实例字段等(或者实际的比较实现是什么),并且发现它们是相等的。到现在为止还挺好。

但是,通过合约,只有在知道bar.equals(foo)也将返回true时才能返回true。由于bar可以是Super的任何子类,因此不清楚equals()方法是否将被覆盖(如果可能的话)。因此,为了确保您的实施是正确的,您需要对称编写并确保这两个对象是同一个类。

更根本的,不同类的对象不能真正被认为是相等的 - 因为在这种情况下,只有它们中的一个可以被插入到一个HashSet<Sub>,例如。

+0

Long和Integer是不同的类类型,因此使用equals比较它们将始终返回false。 – 2009-01-15 17:42:16

0

所以,你的代码应该是....

java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>(); 
m.put(5L, "Five"); // compiler barfs on m.put(5, "Five") 
System.out.println(m.containsKey(5L)); // true 

你忘记了,Java是自动装箱你的代码,所以上面的代码会被equivelenet到

java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>(); 
m.put(new Long(5L), "Five"); // compiler barfs on m.put(5, "Five") 
System.out.println(m.containsKey(new Long(5))); // true 
System.out.println(m.containsKey(new Long(5L))); // true 

所以一部分的问题在于自动装箱。另一部分是你有不同的类型,如其他海报所述。

0

另一个答案充分解释了为什么它失败,但没有一个解决了如何编写在这个问题上不太容易出错的代码。不得不记住添加类型转换(无编译器帮助),带L等后缀的原语是不可接受的恕我直言。

我强烈建议使用GNU库的集合库,当你有原语时(以及其他许多情况下)。例如,有一个TLongLongHashMap将事物存储为原始的long。因此,你永远不会结束与拳击/拆箱,并永远不会以意外的行为结束:

TLongLongHashMap map = new TLongLongHashMap(); 
map.put(1L, 45L); 
map.containsKey(1); // returns true, 1 gets promoted to long from int by compiler 
int x = map.get(1); // Helpful compiler error. x is not a long 
int x = (int)map.get(1); // OK. cast reassures compiler that you know 
long x = map.get(1); // Better. 

等等。没有必要获得正确的类型,并且编译器会给你一个错误(你可以纠正或覆盖),如果你做一些愚蠢的事情(尝试在int中存储一个long)。

自动铸造的规则意味着比较正常工作,以及:

if(map.get(1) == 45) // 1 promoted to long, 45 promoted to long...all is well 

作为奖励,开销内存和运行时的性能要好得多。