Avatar ·

В чем разница между == и equals() в Java?

Привет, коллеги! Я только начал изучать Java и столкнулся с непонятной ситуацией при сравнении строк. Написал простой код: ```java String a = "Привет"; String b = "Привет"; String c = new String("Привет"); System.out.println(a == b); // true System.out.println(a == c); // false System.out.println(a.equals(c)); // true ``` Почему `a == b` возвращает `true`, но `a == c` — `false`? И при чем тут `equals()`? Я думал, что оба оператора сравнивают значения. В чем подвох? Объясните, пожалуйста, как новичку, когда использовать `==`, а когда `equals()`. Особенно интересует, как это работает со строками и другими объектами. <<>>

Главное отличие: сравнение ссылок vs сравнение содержимого

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

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

Почему в вашем примере a == b — true?

Это связано с интернированием строк (String interning). В Java есть пул строк (String pool). Когда вы создаете строку через литерал ("Привет"), JVM проверяет пул: если такая строка уже есть, она возвращает ссылку на существующий объект. Поэтому a и b ссылаются на один и тот же объект в пуле. == показывает true.

А c = new String("Привет") — это явное создание нового объекта в куче (heap), даже если в пуле такая строка уже есть. Поэтому a == c — false, так как это разные объекты.

Метод equals() у класса String переопределен и сравнивает именно последовательность символов. Поэтому a.equals(c) возвращает true — содержимое одинаковое.

Как это работает с другими объектами?

Для пользовательских классов, если вы не переопределили equals(), он будет работать как ==:

class Person {
    String name;
    Person(String name) { this.name = name; }
}

Person p1 = new Person("Иван");
Person p2 = new Person("Иван");
System.out.println(p1 == p2);          // false — разные объекты
System.out.println(p1.equals(p2));     // false — equals из Object тоже сравнивает ссылки

Чтобы сравнивать логически, нужно переопределить equals() (и hashCode() вместе с ним):

class Person {
    String name;
    Person(String name) { this.name = name; }

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

    @Override
    public int hashCode() {
        return Objects.hash(name);
    }
}

Person p1 = new Person("Иван");
Person p2 = new Person("Иван");
System.out.println(p1 == p2);          // false
System.out.println(p1.equals(p2));     // true — теперь сравниваем по name

Практические правила

  • Для примитивов (int, double, boolean и др.) используйте только == — он сравнивает значения.
  • Для строк всегда используйте equals(), если хотите сравнить текст. == может сработать случайно из-за интернирования, но это ненадежно.
  • Для объектов (включая обертки типа Integer): == проверяет, один ли это объект; equals() — одинаковое ли содержимое (если метод переопределен).
  • Проверка на null: == безопасен (можно писать obj == null), а equals() вызовет NullPointerException, если объект null. Используйте Objects.equals(a, b) для безопасного сравнения.

Итог

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

<<>>

Дополнение: как не запутаться с Integer и другими обертками

Еще один частый источник ошибок — сравнение объектов-оберток, например Integer. Тут тоже есть "кэш" похожий на пул строк:

Integer x = 127;
Integer y = 127;
Integer a = 128;
Integer b = 128;

System.out.println(x == y);  // true — из-за кэша Integer от -128 до 127
System.out.println(a == b);  // false — выходят за пределы кэша
System.out.println(a.equals(b)); // true — правильный способ

Мораль: никогда не используйте == для сравнения оберток (Integer, Long, Boolean и т.д.), если вам не нужно проверить, что это буквально один и тот же объект. Всегда вызывайте .equals() или распаковывайте в примитив (a.intValue() == b.intValue()).

Также помните: для enum можно безопасно использовать ==, так как каждый экземпляр enum существует в единственном экземпляре (JVM гарантирует). Но для единообразия кода часто используют equals() — это тоже работает.