繁体   English   中英

为什么Long.valueOf(0).equals(Integer.valueOf(0))为false?

[英]Why is Long.valueOf(0).equals(Integer.valueOf(0)) false?

这个问题是由奇怪的HashMap.put()行为引起的

我想我明白为什么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值在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 fooSub类的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则检查传递的对象是longshortfloatdouble ,...,然后转换和比较。 如果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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM