Почему это ==, а не `equals ()`? - PullRequest
21 голосов
/ 11 августа 2009

Я немного озадачен тем, как 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 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]

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

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

Ответы [ 8 ]

21 голосов
/ 11 августа 2009

(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_

EDIT

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;
}
11 голосов
/ 11 августа 2009

причина

X == y

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

В то время как

X.equals(y)

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

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

6 голосов
/ 11 августа 2009

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

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

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

3 голосов
/ 11 августа 2009

Ваша проблема здесь не только в том, как он обрабатывает ==, но и в автобоксах ... Когда вы сравниваете 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;
    }

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

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

1 голос
/ 11 августа 2009

Немного подробнее о том, как работает автобокс и как кэшируются "малые" объекты 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 относятся к двум разным объектам Integer.

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

1 голос
/ 11 августа 2009

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

1 голос
/ 11 августа 2009

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

1 голос
/ 11 августа 2009

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());
...