[英]Why is Long.valueOf(0).equals(Integer.valueOf(0)) false?
这个问题是由奇怪的HashMap.put()行为引起的
我想我明白为什么Map<K,V>.put
采用K
但Map<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
值在int
范围内并且值相等,那么返回true是不是可以解决这个问题?
这是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值的隐式加宽,但是对于对象类型,没有用于从Integer隐式转换为Long的规则。
另外看看Java Puzzlers ,它有很多类似的例子。
一般来说,尽管在equals()的约定中没有严格表达,但是对象不应该认为自己等于不是完全相同类的另一个对象(即使它是一个子类)。 考虑对称属性 - 如果a.equals(b)为真,那么b.equals(a)也必须为真。
让我们有两个对象,类Super
foo
和Sub
类的bar
,它扩展了Super
。 现在考虑在Super中实现equals()
,特别是当它被称为foo.equals(bar)
。 Foo只知道bar是强类型的Object
,所以要获得准确的比较,需要检查它是Super的实例,如果不是则返回false。 是的,所以这部分很好。 它现在比较所有实例字段等(或实际的比较实现),并找到它们相等。 到现在为止还挺好。
但是,根据合约,如果知道bar.equals(foo)也将返回true,则它只能返回true。 由于bar可以是Super的任何子类,因此不清楚equals()方法是否会被覆盖(如果可能的话)。 因此,为了确保您的实现是正确的,您需要对称地编写它并确保两个对象是同一个类。
更基本的是,不同类的对象实际上不能被认为是相等的 - 因为在这种情况下,例如,只有其中一个可以插入到HashSet<Sub>
。
是的,但这一切都取决于比较算法以及转换的距离。 例如,当你尝试m.Contains("5")
时,你想要发生什么? 或者如果你传递一个数组,其中5作为第一个元素? 简单地说,它似乎是“如果类型不同,键是不同的”。
然后以object
作为键来获取集合。 如果你put
5L
,然后尝试获得5
, "5"
,...,你想要发生什么? 如果你put
一个5L
和一个5
以及一个"5"
并想要检查一个5F
怎么办?
由于它是一个通用集合(或模板化,或任何你想称之为),它必须检查并对某些值类型进行一些特殊的比较。 如果K是int
则检查传递的对象是long
, short
, float
, double
,...,然后转换和比较。 如果K是float
那么检查传递的对象是否...
你明白了。
如果类型不匹配,另一种实现可能是抛出异常,但我经常希望它能做到。
你的问题看起来似乎是合理的,但是如果不是它的合同,它将违反equals()的一般约定,对于两种不同的类型返回true。
与C ++不同,Java语言设计的一部分是对象永远不会隐式转换为其他类型。 这是使Java成为一种简单易懂的语言的一部分。 C ++复杂性的一个合理部分来自隐式转换及其与其他功能的交互。
此外,Java在基元和对象之间具有明显且可见的二分法。 这与其他语言不同,在这些语言中,这种差异作为优化隐藏在幕后。 这意味着你不能指望Long和Integer像long和int那样行事。
可以编写库代码来隐藏这些差异,但这实际上可能会使编程环境不那么一致。
所以你的代码应该是....
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
所以问题的一部分是自动装箱。 另一部分是你有其他海报所说的不同类型。
其他答案充分解释了它失败的原因,但没有一个解决如何编写在此问题上不易出错的代码。 必须记住添加类型转换(没有编译器帮助),后缀基元与L等等是不可接受的恕我直言。
我强烈建议在使用原语时(以及许多其他情况下)使用GNU trove库集合。 例如,有一个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
作为奖励,内存开销和运行时性能要好得多。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.