Почему Long.valueOf (0) .equals (Integer.valueOf (0)) ложно? - PullRequest
16 голосов
/ 15 января 2009

Этот вопрос вызван странным поведением 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

Неужели это не удалось решить путем возврата значения true, если значение Long находилось в пределах диапазона int и значения равны?

Ответы [ 7 ]

24 голосов
/ 15 января 2009

Вот источник из 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 , в нем много примеров, подобных этому.

6 голосов
/ 15 января 2009

Вообще говоря, хотя это не строго выражено в контракте для equals () , объекты не должны считать себя равными другому объекту, который не относится к тому же классу (даже если это подкласс ). Рассмотрим симметричное свойство - если a.equals (b) имеет значение true, то b.equals (a) также должно быть истинным.

Давайте иметь два объекта: foo класса Super и bar класса Sub, который расширяет Super. Теперь рассмотрим реализацию equals() в Super, особенно когда он называется foo.equals(bar). Foo знает только, что bar строго типизирована как Object, поэтому для точного сравнения необходимо проверить, что это экземпляр Super, а если нет, вернуть false. Да, так что эта часть в порядке. Теперь он сравнивает все поля экземпляра и т. Д. (Или независимо от фактической реализации сравнения) и находит их равными. Пока все хорошо.

Однако, согласно договору, он может вернуть true, только если знает, что bar.equals (foo) также вернет true. Поскольку bar может быть любым подклассом Super, неясно, будет ли переопределен метод equals () (и, возможно, будет). Таким образом, чтобы убедиться, что ваша реализация верна, вам нужно написать ее симметрично и убедиться, что два объекта одного класса.

Более фундаментально, объекты разных классов не могут считаться равными - поскольку в этом случае только один из них может быть вставлен в HashSet<Sub>, например.

5 голосов
/ 15 января 2009

Да, но все сводится к алгоритму сравнения и тому, как далеко нужно выполнить преобразования. Например, что вы хотите, чтобы вы попробовали m.Contains("5")? Или если вы передадите ему массив с 5 в качестве первого элемента? Проще говоря, похоже, что он зашит «если разные типы, разные ключи».

Затем возьмите коллекцию с ключом object. Что вы хотите, чтобы произошло, если вы put a 5L, а затем попытаться получить 5, "5", ...? Что если вы put a 5L и 5 и "5" и захотите проверить на 5F?

Поскольку это универсальная коллекция (или шаблонная, или как вы хотите ее называть), она должна будет проверить и выполнить какое-то специальное сравнение для определенных типов значений. Если K равно int, проверьте, передан ли объект long, short, float, double, ..., затем выполните преобразование и сравните. Если K float, проверьте, передан ли объект ...

Вы получаете точку.

Другая реализация могла бы создать исключение, если типы не совпадают, и я часто хотел бы, чтобы это соответствовало.

4 голосов
/ 15 января 2009

Ваш вопрос кажется разумным, но было бы нарушением общих правил для equals (), если не его контракт, возвращать true для двух разных типов.

0 голосов
/ 29 февраля 2012

Другие ответы адекватно объясняют, почему он терпит неудачу, но ни один из них не рассматривает, как написать код, который менее подвержен ошибкам вокруг этой проблемы. Запоминание добавления приведений типов (без помощи компилятора), суффиксов примитивов с L и т. Д. Просто неприемлемо, ИМХО.

Я настоятельно рекомендую использовать библиотеку коллекций GNU, если у вас есть примитивы (и во многих других случаях). Например, есть TLongLongHashMap, который хранит вещи как единое целое. В результате вы никогда не получите бокс / распаковку и не столкнетесь с неожиданным поведением:

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.

и так далее. Нет необходимости понимать тип правильно, и компилятор выдаст вам ошибку (которую вы можете исправить или переопределить), если вы делаете что-то глупое (попытайтесь сохранить long в int).

Правила автоматического приведения означают, что сравнения также работают правильно:

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

В качестве бонуса значительно улучшаются затраты памяти и производительность во время выполнения.

0 голосов
/ 16 января 2009

Так твой код должен быть ....

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 автоматически блокирует ваш код, поэтому приведенный выше код будет эквивалентен

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

Так что частью вашей проблемы является автобокс. Другая часть заключается в том, что у вас есть другие типы, как указано в других плакатах.

0 голосов
/ 15 января 2009

Часть дизайна языка Java заключалась в том, что Objects никогда неявно конвертировал в другие типы, в отличие от C ++. Это было частью создания Java маленьким простым языком. Разумная часть сложности C ++ обусловлена ​​неявными преобразованиями и их взаимодействием с другими функциями.

Кроме того, Java имеет четкую и видимую дихотомию между примитивами и объектами. Это отличается от других языков, где эта разница скрыта под прикрытием в качестве оптимизации. Это означает, что вы не можете ожидать, что Long и Integer будут вести себя как long и int.

Библиотечный код может быть написан, чтобы скрыть эти различия, но на самом деле это может навредить, делая среду программирования менее последовательной.

...