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

时间:2009-01-15 08:12:16

标签: java equals

此问题由strange HashMap.put() behaviour

提示

我想我理解为什么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无法解决此问题吗?

7 个答案:

答案 0 :(得分: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值的隐式加宽,但是对于对象类型,没有用于从Integer隐式转换为Long的规则。

同时查看Java Puzzlers,它有很多类似的例子。

答案 1 :(得分:6)

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

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

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

更重要的是,不同类的对象实际上不能被认为是平等的 - 因为在这种情况下,例如,只有其中一个可以插入到HashSet<Sub>中。

答案 2 :(得分:5)

是的,但这一切都归结为比较算法以及转换的距离。例如,当您尝试m.Contains("5")时,您想要发生什么?或者如果你传递一个数组,其中5作为第一个元素?简单来说,似乎是“如果类型不同,密钥不同”,它就会被连线。

然后选择一个以object为关键字的集合。如果您put 5L,然后尝试获取5"5",......,您希望发生什么?如果您put 5L以及5"5"并且想要检查5F,该怎么办?

由于它是一个通用集合(或模板化,或者你想要调用它的任何东西),它必须检查并对某些值类型进行一些特殊的比较。如果K为int,则检查传递的对象是longshortfloatdouble,...,然后进行转换和比较。如果K为float,则检查传递的对象是否为......

你明白了。

另一种实现可能是在类型不匹配的情况下抛出异常,但我经常希望这样做。

答案 3 :(得分:4)

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

答案 4 :(得分:0)

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

此外,Java在基元和对象之间存在明显可见的二分法。这与其他语言不同,在这些语言中,这种差异作为优化隐藏在幕后。这意味着你不能指望Long和Integer像long和int那样行事。

可以编写库代码来隐藏这些差异,但这实际上可能会使编程环境不那么一致。

答案 5 :(得分: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

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

答案 6 :(得分:0)

其他答案充分解释了它失败的原因,但没有一个解决如何编写在此问题上不易出错的代码。必须记住添加类型转换(没有编译器帮助),后缀基元与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

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