Перейти к основному содержимому

Разница между == и equals() в Java

· 6 мин. чтения

1. Обзор

В этом руководстве мы опишем две основные проверки равенства в Java — равенство ссылок и равенство значений. Мы сравним их, покажем примеры и выделим ключевые различия между ними.

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

2. Эталонное равенство

Мы начнем с понимания сравнения ссылок, которое представлено оператором равенства ( == ). Равенство ссылок возникает, когда две ссылки указывают на один и тот же объект в памяти.

2.1. Оператор равенства с примитивными типами

Мы знаем, что примитивные типы в Java — это простые необработанные значения, не относящиеся к классу. Когда мы используем оператор равенства с примитивными типами, мы просто сравниваем их значения:

int a = 10;
int b = 15;
assertFalse(a == b);

int c = 10;
assertTrue(a == c);

int d = a;
assertTrue(a == d);

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

Давайте теперь выполним нулевые проверки:

int e = null; // compilation error
assertFalse(a == null); // compilation error
assertFalse(10 == null); // compilation error

Java запрещает присваивать значение null примитиву. Как правило, мы не можем выполнять какие -либо проверки на null с помощью оператора равенства для примитивных переменных или значений.

2.2. Оператор равенства с типами объектов

Что касается типов объектов в Java , оператор равенства выполняет только ссылочное сравнение на равенство , игнорируя значения объекта. Прежде чем мы реализуем тесты, давайте создадим простой пользовательский класс:

public class Person {
private String name;
private int age;

// constructor, getters, setters...
}

Теперь давайте инициализируем некоторые объекты класса и проверим результаты оператора равенства:

Person a = new Person("Bob", 20);
Person b = new Person("Mike", 40);
assertFalse(a == b);

Person c = new Person("Bob", 20);
assertFalse(a == c);

Person d = a;
assertTrue(a == d);

Результаты совсем другие, чем раньше. Вторая проверка возвращает false , тогда как для примитивов мы получили true . Как мы упоминали ранее, оператор равенства игнорирует внутренние значения объекта при сравнении. Он только проверяет, ссылаются ли две переменные на один и тот же адрес памяти .

В отличие от примитивов, мы можем использовать null при работе с объектами:

assertFalse(a == null); Person e = null; assertTrue(e == null);

Используя оператор равенства и сравнивая null, мы проверяем, не инициализирован ли уже объект, присвоенный переменной .

3. Цените равенство

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

Это сравнивает значения и тесно связано с методом equals() объекта . Как и прежде, давайте сравним его использование с примитивами и объектными типами, обратив внимание на ключевые отличия.

3.1. Метод equals() с примитивными типами

Как мы знаем, примитивы являются базовыми типами с одним значением и не реализуют никаких методов. Поэтому невозможно напрямую вызвать метод equals() с помощью примитивов :

int a = 10; assertTrue(a.equals(10)); // compilation error

Однако, поскольку у каждого примитива есть свой собственный класс-оболочка , мы можем использовать механизм упаковки, чтобы преобразовать его в его объектное представление. Затем мы можем легко вызвать метод equals() , как если бы мы использовали типы объектов:

int a = 10;
Integer b = a;

assertTrue(b.equals(10));

3.2. Метод equals() с типами объектов

Вернемся к нашему классу Person . Чтобы метод equals() работал правильно, нам нужно переопределить метод в пользовательском классе, учитывая поля, содержащиеся в классе:

public class Person {
// other fields and methods omitted

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
}

Во-первых, метод equals() возвращает true , если заданное значение имеет ту же ссылку, что и проверяется оператором ссылки. Если нет, начинаем проверку на равенство.

Далее мы проверяем равенство объектов Class для обоих значений. Мы возвращаем false , если они разные. В противном случае продолжаем проверку на равенство. Наконец, мы возвращаем объединенный результат сравнения каждого свойства по отдельности.

Теперь давайте изменим предыдущий тест и проверим результаты:

Person a = new Person("Bob", 20);
Person b = new Person("Mike", 40);
assertFalse(a.equals(b));

Person c = new Person("Bob", 20);
assertTrue(a.equals(c));

Person d = a;
assertTrue(a.equals(d));

Как мы видим, вторая проверка возвращает true , а не ссылочное равенство. Наш переопределенный метод equals() сравнивает внутренние значения объектов.

Если мы не переопределяем метод equals() , используется метод из родительского класса Object . Поскольку метод Object.equals() выполняет только проверку равенства ссылок, поведение может отличаться от ожидаемого при сравнении объектов Person .

Хотя выше мы не показывали метод hashCode() , следует отметить, что важно переопределять его всякий раз, когда мы переопределяем метод equals () , чтобы обеспечить согласованность между этими методами . ``

4. Нулевое равенство

Напоследок проверим, как работает метод equals() с нулевым значением:

Person a = new Person("Bob", 20);
Person e = null;
assertFalse(a.equals(e));
assertThrows(NullPointerException.class, () -> e.equals(a));

Когда мы проверяем его с помощью метода equals() на другом объекте, мы получаем два разных результата в зависимости от порядка этих переменных. Последний оператор вызывает исключение, потому что мы вызываем метод equals() для нулевой ссылки. Чтобы исправить последнее утверждение, мы должны сначала вызвать проверку оператора равенства:

assertFalse(e != null && e.equals(a));

Теперь левая часть условия возвращает false , что делает весь оператор равным false , предотвращая создание исключения NullPointerException . Поэтому мы должны не забыть сначала проверить, что значение, для которого мы вызываем метод equals() , не равно null , иначе это может привести к досадным ошибкам.

Более того, начиная с Java 7, мы можем использовать статический метод Objects#equals() , безопасный для нулей, для проверки на равенство: ``

assertFalse(Objects.equals(e, a));
assertTrue(Objects.equals(null, e));

Этот вспомогательный метод выполняет дополнительные проверки, чтобы предотвратить создание исключения NullPointerException , возвращая значение true , если оба параметра имеют значение null .

5. Вывод

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

Чтобы проверить ссылочное равенство, мы используем оператор == . Этот оператор работает несколько иначе для примитивных значений и объектов. Когда мы используем оператор равенства с примитивами, он сравнивает значения. С другой стороны, когда мы используем его для объектов, он проверяет ссылки на память. Сравнивая его с нулевым значением, мы просто проверяем, что объект инициализирован в памяти.

Чтобы выполнить проверку равенства значений в Java, мы используем метод equals() , унаследованный от Object . Примитивы — это простые значения, не относящиеся к классу, поэтому этот метод нельзя вызывать без переноса.

Нам также нужно помнить, что метод equals() следует вызывать только для созданного объекта. В противном случае будет выброшено исключение. Чтобы предотвратить это, если мы подозреваем нулевое значение, мы должны проверить значение с помощью оператора == .

Как всегда, исходный код примеров доступен на GitHub .