🔥 🚀 Важно для всех, кто работает с Java! 🔥
На JavaRocks ты найдешь уникальные туториалы, практические задачи и редкие книги, которых не найти в свободном доступе. Присоединяйся к нашему Telegram-каналу JavaRocks — стань частью профессионального сообщества!
В этой статье речь пойдет о важной и довольно интересной теме — о сравнении объектов в Java, на примере строк и метода equals. Вы можете выбрать удобный для вас формат — посмотреть видеоурок на английском языке или прочитать текстовую версию ниже.
Итак, в каких случаях объект A будет равен объекту B?
Давайте попробуем написать пример:
public class Car {
String model;
int maxSpeed;
public static void main(String[] args) {
Car car1 = new Car();
car1.model = "Ferrari";
car1.maxSpeed = 300;
Car car2 = new Car();
car2.model = "Ferrari";
car2.maxSpeed = 300;
System.out.println(car1 == car2);
}
}Run CodeВывод в консоль:
false
Стоп. Почему же эти две машины не равны? Мы присвоили им одинаковые значения полей, но результат сравнения все равно false.
Все очень просто: оператор == сравнивает ссылки на объекты, а не их содержимое. Два объекта могут иметь даже 500 полей с одинаковыми значениями, но при их сравнении все равно получим false, потому что ссылки car1 и car2 указывают на разные участки памяти — то есть, на два разных объекта.
Представьте, что вы сравниваете людей. Конечно, где-то в мире точно есть человек с таким же именем, цветом глаз, возрастом, ростом, цветом волос и т. д. Это делает вас похожими во многих отношениях, но вы все равно не близнецы — и уж точно вы не один и тот же человек.

Оператор == использует примерно ту же логику, когда мы применяем его для сравнения двух объектов. Он проверяет, один и тот же это объект в памяти или нет.
Но что, если нужно, чтобы программа использовала другую логику?
Например, предположим, что программа занимается анализом ДНК. Она сравнивает генетический код двух людей и должна определить, являются ли они близнецами.
public class Man {
int geneticCode;
public static void main(String[] args) {
Man man1 = new Man();
man1.geneticCode = 1111222233;
Man man2 = new Man();
man2.geneticCode = 1111222233;
System.out.println(man1 == man2);
}
}Run CodeВывод в консоль:
false
Мы получаем тот же самый результат (ведь мы почти ничего не изменили), но теперь эта логика нас не устраивает. В конце концов, в реальной жизни анализ ДНК должен давать нам 100-процентную гарантию, что перед нами близнецы. Но наша программа и оператор == говорят об обратном. Как изменить это поведение и научить программу возвращать правильный результат при совпадении ДНК?
В Java для таких случаев есть специальный метод — equals().
Как и метод toString(), equals() принадлежит классу Object — базовому классу в Java, от которого происходят все остальные классы.
Но сам по себе метод equals() не меняет поведение нашей программы:
public class Man {
String geneticCode;
public static void main(String[] args) {
Man man1 = new Man();
man1.geneticCode = "111122223333";
Man man2 = new Man();
man2.geneticCode = "111122223333";
System.out.println(man1.equals(man2));
}
}Run CodeКонсольный вывод:
false
Тот же результат, что и с ==. Тогда зачем вообще нужен этот метод?
Все просто. сейчас мы используем реализацию equals() из класса Object по умолчанию.
Если заглянуть в код, то увидим следующее:
public boolean equals(Object obj) {
return (this == obj);
}Run CodeВот почему поведение программы не изменилось — поведение equals() по умолчанию такое же, как у ==, то есть сравниваются ссылки, а не содержимое объектов.
Но фишка этого метода в том, что его можно переопределить — написать свой собственный метод equals(), придав ему нужное нам поведение!
В настоящее время нам не нравится, что man1.equals(man2) фактически работает также, как и man1 == man2.
Вот что мы сделаем в этой ситуации:
public class Man {
int dnaCode;
public boolean equals(Man man) {
return this.dnaCode == man.dnaCode;
}
public static void main(String[] args) {
Man man1 = new Man();
man1.dnaCode = 1111222233;
Man man2 = new Man();
man2.dnaCode = 1111222233;
System.out.println(man1.equals(man2));
}
}Run CodeКонсольный вывод:
true
Теперь результат совершенно другой! Написав собственный метод equals() и использовав его вместо стандартного, мы добились правильного поведения. Теперь, если у двух людей одинаковая ДНК, программа сообщает: “DNA analysis has proven they are twins” (“анализ ДНК подтвердил, что они близнецы”) и возвращает true!
Переопределив метод equals() в классах, можно легко настроить любую необходимую логику сравнения объектов.
Сравнение строк в Java
Почему мы рассматриваем сравнения строк отдельно от других объектов? Дело в том, что строки — это особая тема в программировании.
Во-первых, если посмотреть на все когда-либо написанные на Java программы, то окажется, что около 25 % объектов в них — это строки. Поэтому эта тема такая важная.
Во-вторых, процесс сравнения строк сильно отличается от сравнения других объектов.
Рассмотрим простой пример:
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CodeGym is the best website for learning Java!");
System.out.println(s1 == s2);
}
}Run CodeКонсольный вывод:
false
Но почему false? Ведь строки абсолютно одинаковые, слово в слово :/
Вероятно, вы уже догадались, почему: потому что оператор == сравнивает ссылки, а не содержимое! Очевидно, что у s1 и s2 разные адреса в памяти. Теперь давайте поменяем наш пример:
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = "CodeGym is the best website for learning Java!";
System.out.println(s1 == s2);
}
}Run CodeТеперь у нас снова две переменные, но результат противоположный.
Консольный вывод:
true
Запутались окончательно? Давайте разбираться, что происходит.
Оператор == действительно сравнивает адреса в памяти. Это всегда так, и в этом нет никаких сомнений. Это значит, что если s1 == s2 возвращает true, то эти две строки имеют одинаковый адрес. И это действительно так!
Пришло время познакомиться со специальной областью памяти для строк: string pool (пул строк).

Пул строк — это область памяти, в которой хранятся все строковые значения, созданные в программе.
Зачем он нужен? Как мы уже говорили, строки составляют значительную часть всех объектов. Любая большая программа создает множество строк. Пул строк был создан для экономии памяти: строки помещаются в специальную область, и если такая же строка уже существует, то используется существующая — нет необходимости каждый раз выделять дополнительную память.
Каждый раз, когда вы пишете String = "...", программа проверяет, есть ли идентичная строка в пуле строк. Если есть — новая строка создаваться не будет, а переменная просто будет ссылаться на уже существующую.
Поэтому, когда мы написали
String s1 = "CodeGym is the best website for learning Java!";
String s2 = "CodeGym is the best website for learning Java!";Run Codes2 указывает на то же место, что и s1. Первый оператор создает новый объект в пуле строк, а второй оператор просто обращается к той же области памяти, что и s1.
Можно создать еще 500 одинаковых строк — результат не изменится.
Но если все это правда, то почему не сработал предыдущий пример?
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CodeGym is the best website for learning Java!");
System.out.println(s1 == s2);
}
}Run CodeДумаю, ваша интуиция уже подсказывает вам ответ =) Попробуйте предположить, прежде чем читать дальше.
Видно, что эти две строки были объявлены по-разному. Одна с помощью оператора new, а другая без него. Именно в этом кроется разгадка. Когда объект создается через new, под него всегда выделяется новая область памяти. Такая строка не попадает в пул строк, даже если ее текст полностью совпадает с уже существующей строкой.
То есть, если мы напишем следующий код:
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = "CodeGym is the best website for learning Java!";
String s3 = new String("CodeGym is the best website for learning Java!");
}
}Run Codeв памяти это будет выглядеть следующим образом:

И каждый раз, когда вы создаете новый объект с помощью new, под него выделяется новая область памяти, даже если текст строки точно такой же!
Похоже, с оператором == мы разобрались. Но что делать с методом equals()?
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CodeGym is the best website for learning Java!");
System.out.println(s1.equals(s2));
}
}Run CodeКонсольный вывод:
true
Интересно. Мы уверены, что s1 и s2 ссылаются на разные участки памяти. Но метод equals() по-прежнему утверждает, что они равны. Почему?
Помните, мы говорили, что метод equals() можно переопределить, чтобы сравнивать объекты по нужным критериям?
Именно так и сделано в классе String: метод equals() переопределен, и вместо ссылок он сравнивает последовательность символов в строках. Если текст совпадает — неважно, где эти строки хранятся и как они были созданы — результат будет true.
Кстати, Java позволяет выполнять сравнение строк без учета регистра.
Обычно, если одна из строк содержит все заглавные буквы, то результатом сравнения будет false:
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CODEGYM IS THE BEST WEBSITE FOR LEARNING JAVA!");
System.out.println(s1.equals(s2));
}
}Run CodeКонсольный вывод:
false
Для сравнения строк без учета регистра в классе String существует метод equalsIgnoreCase(). Его удобно использовать, если важно сравнить только последовательность определенных символов, а не их регистр. Например, это может пригодиться при сравнении адресов:
public class Main {
public static void main(String[] args) {
String address1 = "2311 Broadway Street, San Francisco";
String address2 = new String("2311 BROADWAY STREET, SAN FRANCISCO");
System.out.println(address1.equalsIgnoreCase(address2));
}
}Run CodeВ этом случае очевидно, что речь идет об одном и том же адресе, поэтому логично использовать метод equalsIgnoreCase().
Метод String.intern()
У класса String есть еще один интересный метод — intern();
Метод intern() работает непосредственно с пулом строк. Что происходит, если вызвать его на какой-либо строке?
- Проверяется, есть ли такая строка в пуле строк.
- Если есть, то возвращается ссылка на эту строку в пуле.
- Если нет, он добавляет строку в пул строк и возвращает ссылку на нее.
После того применения метода intern() к строке, созданной через new, можно сравнивать ее с другой строкой из пула с помощью оператора ==.
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CodeGym is the best website for learning Java!");
System.out.println(s1 == s2.intern());
}
}Run CodeКонсольный вывод:
true
Раньше, когда мы сравнивали эти строки без intern(), результат был false. Теперь метод intern() проверяет, есть ли строка "CodeGym is the best website for learning Java!" в пуле. Конечно, есть — она создана так:
String s1 = "CodeGym is the best website for learning Java!";Мы проверяем, указывают ли s1 и s2.intern() на одну и ту же область памяти. И да — указывают 🙂
В общем, запомните и применяйте это важное правило:
ВСЕГДА используйте метод equals() для сравнения строк!
Когда мы сравниваем строки, чаще всего мы хотим сравнить текст, а не ссылки на объекты. Именно это и делает equals().
Перевод статьи «Java Compare Strings».
