简体   繁体   English

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

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

This questions is prompted by strange HashMap.put() behaviour 这个问题是由奇怪的HashMap.put()行为引起的

I think I understand why Map<K,V>.put takes a K but Map<K,V>.get takes an Object , it seems not doing so will break too much existing code. 我想我明白为什么Map<K,V>.put采用KMap<K,V>.get采用一个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 Long value was withing int range and the values are equal? 如果Long值在int范围内并且值相等,那么返回true是不是可以解决这个问题?

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;
}

Ie it needs to be a Long type to be equal. 即它必须是Long类型才能相等。 I think the key difference between: 我认为之间的关键区别是:

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 ,它有很多类似的例子。

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). 一般来说,尽管在equals()的约定中没有严格表达,但是对象不应该认为自己等于不是完全相同类的另一个对象(即使它是一个子类)。 Consider the symmetric property - if a.equals(b) is true, then b.equals(a) must also be true. 考虑对称属性 - 如果a.equals(b)为真,那么b.equals(a)也必须为真。

Let's have two objects, foo of class Super , and bar of class Sub , which extends Super . 让我们有两个对象,类Super fooSub类的bar ,它扩展了Super Now consider the implementation of equals() in Super, specifically when it's called as foo.equals(bar) . 现在考虑在Super中实现equals() ,特别是当它被称为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. Foo只知道bar是强类型的Object ,所以要获得准确的比较,需要检查它是Super的实例,如果不是则返回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. 到现在为止还挺好。

However, by the contract it can only return true if it know that bar.equals(foo) is going to return true as well. 但是,根据合约,如果知道bar.equals(foo)也将返回true,则它只能返回true。 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). 由于bar可以是Super的任何子类,因此不清楚equals()方法是否会被覆盖(如果可能的话)。 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. 因此,为了确保您的实现是正确的,您需要对称地编写它并确保两个对象是同一个类。

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. 更基本的是,不同类的对象实际上不能被认为是相等的 - 因为在这种情况下,例如,只有其中一个可以插入到HashSet<Sub>

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") ? 例如,当你尝试m.Contains("5")时,你想要发生什么? Or if you pass it an array with 5 as the first element? 或者如果你传递一个数组,其中5作为第一个元素? Simply speaking, it appears to be wired up "if the types are different, the keys are different". 简单地说,它似乎是“如果类型不同,键是不同的”。

Then take a collection with an object as the key. 然后以object作为键来获取集合。 What do you want to happen if you put a 5L , then try to get 5 , "5" , ...? 如果你put 5L ,然后尝试获得5"5" ,...,你想要发生什么? What if you put a 5L and a 5 and a "5" and you want to check for a 5F ? 如果你put一个5L和一个5以及一个"5"并想要检查一个5F怎么办?

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 int then check if the object passed is long , short , float , double , ..., then convert and compare. 如果K是int则检查传递的对象是longshortfloatdouble ,...,然后转换和比较。 If K is float then check if the object passed is ... 如果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. 如果类型不匹配,另一种实现可能是抛出异常,但我经常希望它能做到。

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

Part of the Java language design was for Objects to never implicitly convert to other types, unlike C++. 与C ++不同,Java语言设计的一部分是对象永远不会隐式转换为其他类型。 This was part of making Java a small, simple language. 这是使Java成为一种简单易懂的语言的一部分。 A reasonable portion of C++'s complexity comes from implicit conversions and their interactions with other features. C ++复杂性的一个合理部分来自隐式转换及其与其他功能的交互。

Also, Java has a sharp and visible dichotomy between primitives and objects. 此外,Java在基元和对象之间具有明显且可见的二分法。 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. 这意味着你不能指望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. 可以编写库代码来隐藏这些差异,但这实际上可能会使编程环境不那么一致。

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是自动装箱你的代码,所以上面的代码将是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

So a part of your problem is the autoboxing. 所以问题的一部分是自动装箱。 The other part is that you have different types as other posters have stated. 另一部分是你有其他海报所说的不同类型。

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). 我强烈建议在使用原语时(以及许多其他情况下)使用GNU trove库集合。 For example, there is a TLongLongHashMap that stores things interally as primitive longs. 例如,有一个TLongLongHashMap可以将内容存储为原始long。 As a result, you never end up with boxing/unboxing, and never end up with unexpected behaviors: 因此,您永远不会以拳击/拆箱结束,并且永远不会遇到意外行为:

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). 没有必要让类型正确,如果你做了一些愚蠢的事情(尝试在int中存储long),编译器会给你一个错误(你可以纠正或覆盖)。

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. 作为奖励,内存开销和运行时性能要好得多。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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