Подтвердить что ты не робот

Почему Long.valueOf(0).equals(Integer.valueOf(0)) false?

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

4b9b3361

Ответ 1

Вот источник из Long.java

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

т.е. он должен быть длинным, чтобы быть равным. Я думаю, что ключевое различие между:

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

и ваш пример выше, так как с примитивами может возникать неявное расширение значения int, однако с типами объектов нет правил для неявного преобразования из Integer в Long.

Также проверьте Java Puzzlers, у него есть много примеров, похожих на это.

Ответ 2

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

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

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

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

Ответ 3

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

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

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

Вы поняли.

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

Ответ 4

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

Ответ 5

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

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

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

Ответ 6

Итак, код должен быть....

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

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

Ответ 7

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

Я настоятельно рекомендую использовать библиотеку 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.

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

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

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

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