Java 为什么 Long.valueOf(0).equals(Integer.valueOf(0)) 是假的?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/445990/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 14:43:54  来源:igfitidea点击:

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

javaequals

提问by Miserable Variable

This questions is prompted by strange HashMap.put() behaviour

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

I think I understand why Map<K,V>.puttakes a Kbut Map<K,V>.gettakes an Object, it seems not doing so will break too much existing code.

我想我明白为什么Map<K,V>.put需要 aKMap<K,V>.get需要 an Object,似乎不这样做会破坏太多现有代码。

Now we get into a very error-prone scenario:

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

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

Couldn't this have been solved by returning true if the Longvalue was withing intrange and the values are equal?

如果Long值在int范围内并且值相等,这不能通过返回 true 来解决吗?

采纳答案by Michael Barker

Here is the source from Long.java

这是来自 Long.java 的源代码

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

I.e. it needs to be a Long type to be equal. I think the key difference between:

即它需要是一个 Long 类型才能相等。我认为之间的主要区别:

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

and your example above is that with primitives an implicit widening of the int value can occur, however with object types there are no rules for implicitly converting from Integer to a Long.

上面的示例是,对于基元,可能会发生 int 值的隐式扩展,但是对于对象类型,没有将 Integer 隐式转换为 Long 的规则。

Also check out Java Puzzlers, it has a lot of examples similar to this.

另请查看Java Puzzlers,它有很多与此类似的示例。

回答by lc.

Yes, but it all comes down to the comparing algorithm and how far to take the conversions. For example, what do you want to happen when you try m.Contains("5")? Or if you pass it an array with 5 as the first element? Simply speaking, it appears to be wired up "if the types are different, the keys are different".

是的,但这一切都归结为比较算法以及转换的程度。例如,当您尝试时,您希望发生什么m.Contains("5")?或者,如果您将其传递给第一个元素为 5 的数组?简单地说,它似乎是“类型不同,键不同”。

Then take a collection with an objectas the key. What do you want to happen if you puta 5L, then try to get 5, "5", ...? What if you puta 5Land a 5and a "5"and you want to check for a 5F?

然后取一个以 anobject为键的集合。你要什么,如果你发生put一个5L,然后试图让5"5",...?如果你puta5L和 a5和 a"5"并且你想检查 a5F怎么办?

Since it's a generic collection (or templated, or whatever you wish to call it), it would have to check and do some special comparing for certain value types. If K is intthen check if the object passed is long, short, float, double, ..., then convert and compare. If K is floatthen check if the object passed is ...

由于它是一个泛型集合(或模板化的,或任何你想称它的),它必须检查并为某些值类型做一些特殊的比较。如果 Kint则检查传递的对象是否为long, short, float, double, ...,然后进行转换和比较。如果 K 是float然后检查传递的对象是否是...

You get the point.

你明白了。

Another implementation could have been to throw an exception if the types didn't match, however, and I often wish it did.

如果类型不匹配,另一种实现可能是抛出异常,但是,我经常希望这样做。

回答by Phil

Your question seems reasonable on its face, but it would be a violation of the general conventions for equals(), if not its contract, to return true for two different types.

您的问题从表面上看似乎是合理的,但如果不是它的合同,则为两种不同类型返回 true 将违反 equals() 的一般约定。

回答by Darron

Part of the Java language design was for Objects to never implicitly convert to other types, unlike C++. This was part of making Java a small, simple language. A reasonable portion of C++'s complexity comes from implicit conversions and their interactions with other features.

Java 语言设计的一部分是让 Objects 永远不会隐式转换为其他类型,这与 C++ 不同。这是使 Java 成为一种小型、简单语言的一部分。C++ 复杂性的合理部分来自隐式转换及其与其他功能的交互。

Also, Java has a sharp and visible dichotomy between primitives and objects. This is different from other languages where this difference is hidden under the covers as an optimization. This means that you can't expect Long and Integer to act like long and int.

此外,Java 在原语和对象之间有一个清晰可见的二分法。这与其他语言不同,其他语言将这种差异隐藏在优化之下。这意味着您不能期望 Long 和 Integer 表现得像 long 和 int。

Library code can be written to hide these differences, but that can actually do harm by making the programming environment less consistent.

可以编写库代码来隐藏这些差异,但这实际上会降低编程环境的一致性,从而造成伤害。

回答by Andrzej Doyle

Generally speaking, although it is not strictly expressed in the contract for equals(), objects should not consider themselves equal to another object that is not of the exact same class (even if it is a subclass). Consider the symmetric property - if a.equals(b) is true, then b.equals(a) must also be true.

一般来说,虽然在equals()的契约中没有严格表达,但对象不应认为自己等于另一个不属于完全相同类的对象(即使它是一个子类)。考虑对称属性 - 如果 a.equals(b) 为真,则 b.equals(a) 也必须为真。

Let's have two objects, fooof class Super, and barof class Sub, which extends Super. Now consider the implementation of equals()in Super, specifically when it's called as foo.equals(bar). Foo only knows that bar is strongly typed as an Object, so to get an accurate comparison it needs to check it's an instance of Super and if not return false. It is, so this part is fine. It now compares all the instance fields, etc. (or whatever the actual comparison implementation is), and finds them equal. So far, so good.

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

However, by the contract it can only return true if it know that bar.equals(foo) is going to return true as well. Since bar can be any subclass of Super, it's not clear whether the equals() method is going to be overridden (and if probably will be). Thus to be sure that your implementation is correct, you need to write it symmetrically and ensure that the two objects are the same class.

但是,根据约定,只有在知道 bar.equals(foo) 也将返回 true 时,它​​才能返回 true。由于 bar 可以是 Super 的任何子类,因此尚不清楚 equals() 方法是否会被覆盖(如果可能会被覆盖)。因此为了确保你的实现是正确的,你需要对称地编写它并确保两个对象是同一个类。

More fundamentally, objects of different classes cannot really be considered equal - since in this case, only one of them can be inserted into a HashSet<Sub>, for example.

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

回答by Milhous

So you code should be....

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

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

You are forgetting that java is autoboxing your code, so the above code would be equivelenet to

您忘记了 java 正在自动装箱您的代码,因此上面的代码将等同于

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

So a part of your problem is the autoboxing. The other part is that you have different types as other posters have stated.

所以你的问题的一部分是自动装箱。另一部分是您有其他海报所说的不同类型。

回答by Tony K.

The other answers adequately explain why it fails, but none of them address how to write code that is less error prone around this issue. Having to remember to add type-casts (no compiler help), suffix primitives with L and so forth is just not acceptable IMHO.

其他答案充分解释了它失败的原因,但没有一个解决如何编写围绕此问题不易出错的代码。必须记住添加类型转换(没有编译器帮助),用 L 等后缀原语是不可接受的恕我直言。

I highly recommend using the GNU trove library of collections when you have primitives (and in many other cases). For example, there is a TLongLongHashMap that stores things interally as primitive longs. As a result, you never end up with boxing/unboxing, and never end up with unexpected behaviors:

我强烈建议您在拥有原语(以及在许多其他情况下)时使用 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.

and so on. There is no need to get the type right, and the compiler gives you an error (that you can correct or override) if you do something silly (try to store a long in an int).

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

The rules of auto-casting mean that comparisons work properly as well:

自动转换的规则意味着比较也能正常工作:

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

As a bonus, the memory overhead and runtime performance is much better.

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