Почему эти ==, но не `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, - это контр-интуитивно понятный.