Другое

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

Узнайте ключевые различия между оператором == и методом equals() в Java: сравнение ссылок и значений, работа со строками, лучшие практики сравнения объектов.

В чем разница между == и equals() в Java?
Я хотел уточнить, правильно ли я понимаю:

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

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

Содержание

Понимание оператора ==

Оператор == в Java выполняет сравнение ссылок при работе с объектами. Это означает, что он проверяет, указывают ли две переменные на один и тот же объект в куче. Если обе ссылки указывают на один и тот же экземпляр, == возвращает true; иначе — false.

java
String str1 = new String("Hello");
String str2 = str1; // str2 теперь указывает на тот же объект, что и str1
String str3 = new String("Hello");

System.out.println(str1 == str2); // true - одна и та же память
System.out.println(str1 == str3); // false - разные объекты

Для примитивных типов (int, boolean, char, double и т.д.) == работает иначе и фактически сравнивает значения напрямую, а не ссылки:

java
int x = 5;
int y = 5;
int z = new Integer(5); // объект Integer

System.out.println(x == y);     // true - одинаковые значения
System.out.println(x == z);     // true - авторазоблачение сравнивает значения
System.out.println(x == z.intValue()); // true - явное разоблачение

Согласно GeeksforGeeks, оператор == «сравнивает ссылку или адрес памяти объектов в куче, указывают ли они на одно и то же место».


Основы метода equals()

Метод equals() определён в классе Object и предоставляет способ сравнения объектов по их содержимому, а не по адресу памяти. По умолчанию реализация equals() в классе Object ведёт себя точно так же, как оператор == — сравнивает адреса памяти.

Java67 объясняет, что «метод equals() используется для сравнения содержимого объекта», а не его адреса.

Реализация по умолчанию:

java
public boolean equals(Object obj) {
    return (this == obj);
}

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


Ключевые различия между == и equals()

1. Тип сравнения

  • ==: всегда выполняет сравнение ссылок для объектов
  • equals(): выполняет сравнение содержимого, если правильно переопределён, иначе сравнение ссылок по умолчанию

2. Поведение по умолчанию

  • ==: сравнивает адреса памяти одинаково для всех типов объектов
  • equals(): по умолчанию сравнивает адреса памяти (в классе Object), но может быть переопределён

3. Защита от null

  • ==: можно использовать с null (например, obj == null)
  • equals(): вызовет NullPointerException, если вызвать на null

4. Примитивы vs объекты

  • ==: работает как с примитивами, так и с объектами
  • equals(): работает только с объектами (не может быть вызван на примитивах)

Согласно Baeldung, «по умолчанию его реализация сравнивает адреса памяти объектов, поэтому она работает так же, как оператор ==».


Особенности сравнения строк

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

java
String s1 = new String("Java");
String s2 = new String("Java");
String s3 = s1;

System.out.println(s1 == s2);      // false - разные объекты
System.out.println(s1.equals(s2));  // true - одинаковое содержимое
System.out.println(s1 == s3);      // true - один и тот же объект
System.out.println(s1.equals(s3));  // true - одинаковое содержимое и ссылка

Однако есть особый случай для строковых литералов, которые интернированы в пуле строк:

java
String str1 = "Java";  // из пула строк
String str2 = "Java";  // та же ссылка из пула

System.out.println(str1 == str2);      // true - одна и та же ссылка из пула
System.out.println(str1.equals(str2));  // true - одинаковое содержимое

Medium объясняет, что «он не сравнивает содержимое объектов, а сравнивает их адреса памяти» для оператора ==, тогда как equals() класса String сравнивает содержимое.


Сравнение пользовательских объектов

При создании пользовательских классов необходимо переопределить метод equals(), чтобы обеспечить осмысленное сравнение содержимого. Вот правильная реализация:

java
public class Person {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public boolean equals(Object obj) {
        // 1. Проверяем, тот же ли объект
        if (this == obj) return true;
        
        // 2. Проверяем null и тип класса
        if (obj == null || getClass() != obj.getClass()) return false;
        
        // 3. Приводим к нужному типу и сравниваем поля
        Person other = (Person) obj;
        return age == other.age && Objects.equals(name, other.name);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

Согласно SEI CERT Oracle Coding Standard, «при необходимости канонизации объектов может быть целесообразнее использовать пользовательский канонизатор на основе ConcurrentHashMap».


Лучшие практики и рекомендации

Когда использовать ==

  • Проверка идентичности объекта (один и тот же экземпляр)
  • Сравнение примитивных типов
  • Сравнение с null
  • Сценарии, где критична производительность и достаточно сравнения ссылок

Когда использовать equals()

  • Сравнение содержимого строк
  • Сравнение пользовательских объектов по их состоянию
  • Когда важна логическая эквивалентность, а не идентичность
  • Работа с коллекциями, зависящими от equals() (например, HashSet, HashMap)

Правила переопределения equals()

  1. Рефлексивность: x.equals(x) должно возвращать true
  2. Симметричность: если x.equals(y) возвращает true, то y.equals(x) также должно возвращать true
  3. Транзитивность: если x.equals(y) и y.equals(z) возвращают true, то x.equals(z) должно возвращать true
  4. Согласованность: многократные вызовы должны возвращать одинаковый результат
  5. Нулевое значение: x.equals(null) должно возвращать false

Документация EqualsVerifier отмечает, что он «вызывает equals и hashCode многократно на различных перестановках объектов, чтобы убедиться, что они возвращают ожидаемые значения».


Распространённые ошибки и примеры

Ошибка 1: Не переопределить equals() и hashCode()

java
public class Employee {
    private String id;
    
    // Отсутствуют equals() и hashCode()
}

// Это вызовет проблемы в HashMap и HashSet
Map<Employee, String> map = new HashMap<>();
map.put(new Employee("123"), "John");
map.get(new Employee("123")); // возвращает null - не найдено!

Ошибка 2: Использовать == для сравнения строк

java
// Неправильно - сравнивает ссылки, а не содержимое
String input = "hello";
if (input == "hello") { // Может сработать из-за пула строк, но ненадёжно
    // делаем что‑то
}

// Правильно - сравниваем содержимое
String input = "hello";
if (input.equals("hello")) {
    // делаем что‑то
}

Ошибка 3: Забыть проверить null

java
// Риск NullPointerException
if (obj1.equals(obj2)) { // NPE, если obj1 == null

// Безопасный подход
if (obj1 != null && obj1.equals(obj2)) {
    // делаем что‑то
}

Java67 предупреждает, что «== используется для проверки ссылки или адреса памяти объектов, а equals() — для сравнения содержимого объекта».


Заключение

Главное различие между == и equals() в Java сводится к сравнению ссылок против сравнения значений:

  1. Оператор == всегда сравнивает адреса памяти для объектов, что делает его идеальным для проверки идентичности объекта
  2. Метод equals() сравнивает содержимое объекта, если правильно переопределён, что делает его подходящим для логического сравнения
  3. Для примитивов == сравнивает значения напрямую
  4. Класс String переопределяет equals() для сравнения содержимого, в то время как большинство пользовательских классов должны переопределять его сами
  5. Всегда переопределяйте одновременно equals() и hashCode() при реализации сравнения по содержимому

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

Источники

  1. What is the difference between == and equals() in Java? - Stack Overflow
  2. Difference Between == Operator and equals() Method in Java - GeeksforGeeks
  3. Difference between == and equals() method in Java? String Example | Java67
  4. Comparing Objects in Java | Baeldung
  5. == vs .equals() in Java: Reference vs Value Comparison | Medium
  6. EXP50-J: Do not confuse abstract object equality with reference equality - SEI CERT
  7. Understanding the Difference Between == and equals() in Java - DEV Community
  8. Mastering String Comparison in Java: Difference Between “==”, equals(), compareTo(), and Best Practices - Deep Java
  9. Why, what, how? - EqualsVerifier
Авторы
Проверено модерацией
Модерация