Почему это ==, а не `equals()`?

Я немного озадачен тем, как трактует Java == а также equals() когда дело доходит до int, Integer и другие типы номеров. Например:

Integer X = 9000;
int x = 9000;
Short Y = 9000;
short y = 9000;
List<Boolean> results = new ArrayList<Boolean>();
// results.add(X == Y); DOES NOT COMPILE        1)
results.add(Y == 9000);                      // 2)
results.add(X == y);                         // 3)
results.add(X.equals(x));                    // 4)
results.add(X.equals(Y));                    // 5)
results.add(X.equals(y));                    // 6)
System.out.println(results);

Выводы (может быть, вы должны сначала угадать):

[true, true, true, false, false]
  1. Тот X == Y не компилируется - это разные объекты.
  2. Я немного удивлен, что Y == 9 является true, учитывая, что 9 по умолчанию intи учитывая, что 1) даже не компилируется. Обратите внимание, что вы не можете положить int в метод, ожидающий ShortИ все же здесь они равны.
  3. Это удивительно по той же причине, что и две, но, кажется, хуже.
  4. Не удивительно, как x автоматически упакованы в и Integer,
  5. Не удивительно, так как предметы в разных классах не должны быть equal(),
  6. Какие?? X == y является true но X.equals(y) является false? не должны == всегда будь строже equals()?

Буду признателен, если кто-нибудь сможет помочь мне разобраться в этом. По какой причине == и equals() ведут себя так?

Изменить: я изменил 9 на 9000, чтобы показать, что это поведение не связано с какими-либо необычными способами, которыми ведут себя целые числа от -128 до 127.

2-е редактирование: ОК, если вы думаете, что понимаете это, вы должны рассмотреть следующее, просто чтобы убедиться:

Integer X = 9000;
Integer Z = 9000;
short y = 9000;
List<Boolean> results = new ArrayList<Boolean>();
results.add(X == Z);                      // 1)
results.add(X == y);                      // 2)
results.add(X.equals(Z));                 // 3)
results.add(X.equals(y));                 // 4)
System.out.println(results);

выходы:

[false, true, true, false]

Причина, насколько я понимаю, такова:

  1. Другой случай, такой другой.
  2. X unboxed, то же значение, так что равно.
  3. То же значение, так равное.
  4. y не может быть упакован в Integer так не может быть равным.

8 ответов

Решение

Причина для

X == y

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

В то время как

X.equals(y)

это нормальный вызов функции. Как уже упоминалось, y будет автоматически упакован в Short объект. Integer.equals всегда возвращает false, если аргумент не является Integer пример. Это можно легко увидеть, осмотрев реализацию.

Можно утверждать, что это недостаток дизайна.

(small) Целочисленные экземпляры кэшируются, поэтому инвариант x == y сохраняется для небольших экземпляров (на самом деле -127 +128, зависит от JVM):

Integer a = 10;
Integer b = 10;

assert(a == b); // ok, same instance reused

a = 1024;
b = 1024;

assert(a == b); // fail, not the same instance....
assert(a.equals(b)); // but same _value_

РЕДАКТИРОВАТЬ

4) и 5) дают ложь, потому что equals проверить типы: X является целым числом, тогда как Y короткая Это метод java.lang.Integer#equals:

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }

    return false;
}

Мораль истории:

Автобокс / распаковка сбивает с толку, как и продвижение типов. Вместе они создают хорошие загадки, но ужасный код.

На практике редко имеет смысл использовать числовые типы, меньшие, чем int, и я почти склонен настраивать мой компилятор eclipse так, чтобы он отмечал все автобокс и -unboxing как ошибку.

Ваша проблема здесь не только в том, как он обрабатывает ==, но и в автобоксах... Когда вы сравниваете Y и 9, вы сравниваете два одинаковых примитива, в последних двух случаях вы получаете false просто потому, что так работают равные. Два объекта равны, только если они имеют одинаковый вид и имеют одинаковое значение. Когда вы говорите в "X.equals(y)", вы говорите, что он делает Integer.equals(Short), и смотрите на реализацию Integer.equals(), это завершится ошибкой:

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

Из-за автобокса последние два приведут к тому же самому отказу, поскольку они оба будут переданы как Шорты.

Изменить: Забыл одну вещь... В случае результатов. X (= Y); он распакует X и сделает (X.intValue() == y), что так же верно, как и 9 == 9

Я помню, что хорошей практикой для переопределения "равный (объектный объект)" является сначала проверка типа передаваемого параметра. Так что при выполнении perhap это приводит к ложному выводу X.equals(Y). Вы можете проверить код souce, чтобы узнать правду:)

Java преобразует Integer в int автоматически, если это необходимо. То же относится и к коротким. Эта функция называется autoboxing и autounboxing. Вы можете прочитать об этом здесь.

Это означает, что когда вы запускаете код:

int a = 5;
Integer b = a;
System.out.println(a == b);

Java преобразует его в:

int a = 5;
Integer b = new Integer(a);
System.out.println(a == b.valueOf());

Немного подробнее о том, как работает автобокс и как кэшируются "малые" объекты Integer:

Когда примитив int автоматически помещается в Integer, компилятор делает это, заменяя код вызовом Integer.valueOf(...). Итак, следующее:

Integer a = 10;

заменяется компилятором следующим:

Integer a = Integer.valueOf(10);

Метод valueOf(...) класса Integer поддерживает кэш, который содержит объекты Integer для всех значений от -127 до 128. Если вы вызываете valueOf(...) со значением, находящимся в этом диапазоне, метод возвращает предварительный существующий объект из кеша. Если значение находится за пределами диапазона, оно возвращает новый объект Integer, инициализированный указанным значением. (Если вы хотите точно знать, как это работает, найдите файл src.zip в каталоге установки JDK и найдите в нем исходный код класса java.lang.Integer.)

Теперь, если вы сделаете это:

Integer a = 10;
Integer b = 10;
System.out.println(a == b);

вы увидите, что печатается true - но не потому, что a и b имеют одно и то же значение, а потому что a и b ссылаются на один и тот же объект Integer, объект из кэша, возвращенный Integer.valueOf(...).

Если вы измените значения:

Integer a = 200;
Integer b = 200;
System.out.println(a == b);

затем выводится false, потому что 200 выходит за пределы диапазона кэша, и поэтому a и b относятся к двум разным целочисленным объектам.

К сожалению, == используется для равенства объектов для типов значений, таких как классы-обертки и String в Java, - это противоречит интуиции.

Это автоматическое преобразование называется автобоксом.

Другие вопросы по тегам