Почему эти ==, но не `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]
  • То, что X == Y не компилируется, следует ожидать, будучи разными объектами.
  • Я немного удивлен тем, что Y == 9 true, учитывая, что по умолчанию 9 имеет значение int и при условии, что 1) даже не компилируется. Обратите внимание, что вы не можете поместить int в метод, ожидающий Short, но здесь они равны.
  • Это удивительно по той же причине, что и два, но кажется хуже.
  • Не удивительно, поскольку x автобоксация и Integer.
  • Не удивительно, поскольку объекты в разных классах не должны быть equal().
  • Что?? X == Y true, но X.equals(y) есть false? Не должен ли == быть более строгим, чем equals()?

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

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

2 nd Изменить: Хорошо, если вы считаете, что понимаете этот материал, вы должны учитывать следующее: просто

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]

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

  • Различные экземпляры, разные.
  • x unboxed, то такое же значение, равное.
  • То же значение, равное.
  • y не может быть помещен в ячейку Integer, поэтому не может быть равным.

Ответ 1

Причина

X == y

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

В то время как

X.equals(y)

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

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

Ответ 2

(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) дают false, потому что equals проверяют типы: X является целым числом, тогда как Y является Коротким. Это метод java.lang.Integer # равен:

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

    return false;
}

Ответ 3

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

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

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

Ответ 4

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

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

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

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

Ответ 5

Java будет преобразовывать Integer в int автоматически, если это необходимо. То же самое относится к Short. Эта функция называется 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());

Ответ 6

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

Ответ 7

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

Ответ 8

Немного подробнее о том, как работает autoboxing, и о том, как кешируются "маленькие" Целочисленные объекты:

Когда примитивный 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 относятся к двум различным объектам Integer.

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