JavaRush/Java блог/Java Developer/Equals в Java и String compare - Сравнение строк
Автор
Milan Vucic
Репетитор по программированию в Codementor.io

Equals в Java и String compare - Сравнение строк

Статья из группы Java Developer
участников
Привет! Сегодня мы поговорим об очень важной и интересной теме, а именно — сравнении объектов между собой equals() в Java. И действительно, в каких случаях в Java Объект А будет равен Объекту Б? Equals и сравнение строк  - 1Давай попробуем написать пример:
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);
   }
}
Вывод в консоль:

false
Так, стоп. А почему, собственно, эти две машины не равны? Мы задали им одинаковые свойства, но результат сравнения — false. Ответ прост. Оператор == сравнивает не свойства объектов, а ссылки. Будь у двух объектов даже 500 одинаковых свойств, результатом сравнения все равно будет false. Ведь ссылки car1 и car2 указывают на два разных объекта, на два разных адреса. Представь себе ситуацию со сравнением людей. В мире наверняка есть человек, у которого одинаковые с тобой имя, цвет глаз, возраст, рост, цвет волос и т.д. То есть вы во многом похожи, но все-таки вы не близнецы, и тем более не один и тот же человек. Equals и сравнение строк  - 2Примерно такую логику применяет оператор ==, когда с его помощью мы пытаемся сравнить два объекта. Но что, если в твоей программе тебе нужна другая логика? Например, если твоя программа симулирует анализ ДНК. Она должна сравнить код ДНК двух людей, и определить, что это близнецы.
public class Man {

   int 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 == man2);
   }
}
Вывод в консоль:

false
Логично, что результат получился тот же самый (ведь мы особо ничего не меняли), но теперь он нас не устраивает! Ведь в реальной жизни анализ ДНК — стопроцентная гарантия того, что перед нами близнецы. Но наша программа и оператор == говорят нам об обратном. Как нам изменить это поведение и сделать так, чтобы в случае совпадения анализов ДНК программа выдавала правильный результат? Для этого в Java был создан специальный метод — equals().

Метод Equals() в Java

Как и метод toString(), который мы разбирали ранее, equals() принадлежит классу Object самому главному классу в Java, от которого происходят все остальные классы. Однако сам по себе equals() никак не изменит поведение нашей программы:
public class Man {

   String dnaCode;

   public static void main(String[] args) {

       Man man1 = new Man();
       man1.dnaCode = "111122223333";

       Man man2 = new Man();
       man2.dnaCode = "111122223333";

       System.out.println(man1.equals(man2));
   }
}
Вывод в консоль:

false
Точно такой же результат, ну и зачем тогда нужен этот метод? :/ Все просто. Дело в том, что сейчас мы использовали этот метод так, как он реализован в самом классе Object. И если мы зайдем в код класса Object и посмотрим, как в нем реализован данный метод и что он делает, то увидим:
public boolean equals(Object obj) {
   return (this == obj);
}
Вот и причина, почему поведение нашей программы не изменилось! Внутри метода equals() класса Object лежит то же самое сравнение ссылок, ==. Но фишка этого метода в том, что мы можем его переопределить. Переопределить — значит написать свой метод equals() в нашем классе Man и сделать его поведение таким, какое нам нужно! Сейчас нас не устраивает, что проверка 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));

   }
}
Вывод в консоль:

true
Совсем другой результат! Написав свой метод equals() вместо стандартного, мы добились правильного поведения: теперь если у двух людей одинаковый код ДНК, программа говорит нам: “Анализ ДНК показал, что это близнецы” и возвращает true! Переопределяя метод equals() в своих классах, ты можешь легко создавать нужную логику сравнения объектов. Мы затронули сравнение объектов только в общих чертах. Впереди у нас еще будет отдельная большая лекция на эту тему (можешь бегло прочесть ее уже сейчас, если интересно).

String compare в Java - Сравнение строк

Почему мы рассматриваем сравнение строк отдельно от всего остального? Ну, на самом деле, строки в программировании — вообще отдельная песня. Во-первых, если взять все написанные человечеством программы на Java, порядка 25% объектов в них составляют именно они. Поэтому данная тема очень важна. Во-вторых, процесс сравнения строк действительно сильно отличается от остальных объектов. Рассмотрим простой пример:
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush - лучший сайт для изучения Java!";
       String s2 = new String("JavaRush - лучший сайт для изучения Java!");
       System.out.println(s1 == s2);
   }
}
Вывод в консоль:

false
Но почему false? Строки-то ведь абсолютно одинаковые, слово-в-слово :/ Ты можешь предположить: это потому что оператор == сравнивает ссылки! Ведь у s1 и s2 разные адреса в памяти. Если тебя посетила такая мысль, то давай переделаем наш пример:
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush - лучший сайт для изучения Java!";
       String s2 = "JavaRush - лучший сайт для изучения Java!";
       System.out.println(s1 == s2);
   }
}
Сейчас у нас тоже две ссылки, но вот результат изменился на противоположный: Вывод в консоль:

true
Окончательно запутался? :) Давай разбираться. Оператор == действительно сравнивает адреса в памяти. Это правило работает всегда и в нем не надо сомневаться. Значит, если s1 == s2 возвращает true, у этих двух строк одинаковый адрес в памяти. И это действительно так! Настало время познакомиться со специальной областью памяти для хранения строк — пулом строк (String pool) Equals и сравнение строк  - 3Пул строк — область для хранения всех строковых значений, которые ты создаешь в своей программе. Для чего он был создан? Как и говорилось раньше, строки занимают огромную часть от всех объектов. В любой большой программе создается очень много строк. С целью экономии памяти и нужен String Pool — туда помещается строка с нужным тебе текстом, и в дальнейшем вновь созданные ссылки ссылаются на одну и ту же область памяти, нет нужды каждый раз выделять дополнительную память. Каждый раз, когда ты пишешь String = “........”, программа проверяет, есть ли строка с таким текстом в пуле строк. Если есть — новая создана не будет. И новая ссылка будет указывать на тот же адрес в пуле строк, где эта строка хранится. Поэтому когда мы написали в программе
String s1 = "JavaRush - лучший сайт для изучения Java!";
String s2 = "JavaRush - лучший сайт для изучения Java!";
ссылка s2 указывает ровно туда же, куда и s1. Первая команда создала в пуле строк новую строку с нужным нам текстом, а когда дело дошло до второй — она просто сослалась на ту же область памяти, что и s1. Можно сделать хоть еще 500 строк с таким же текстом, результат не изменится. Стоп. Но почему тогда ранее у нас не сработал этот пример?
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush - лучший сайт для изучения Java!";
       String s2 = new String("JavaRush - лучший сайт для изучения Java!");
       System.out.println(s1 == s2);
   }
}
Думаю, интуитивно ты уже догадываешься в чем причина :) Попробуй предположить, прежде чем читать дальше. Ты видишь, что эти две строки были созданы по-разному. Одна — с помощью оператора new, а вторая без него. Именно в этом кроется причина. Оператор new при создании объекта принудительно выделяет для него новую область в памяти. И строка, созданная с помощью new, не попадает в String Pool: она становится отдельным объектом, даже если ее текст полностью совпадает с такой же строкой из String Pool’a. То есть если мы напишем такой код:
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush - лучший сайт для изучения Java!";
       String s2 = "JavaRush - лучший сайт для изучения Java!";
       String s3 = new String("JavaRush - лучший сайт для изучения Java!");
   }
}
В памяти это будет выглядеть вот так: Equals и сравнение строк  - 4И каждый раз при создании нового объекта через new в памяти будет выделяться новая область, даже если текст внутри новых строк будет одинаковым! С оператором == вроде разобрались, а что с нашим новым знакомым — методом equals()?
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush - лучший сайт для изучения Java!";
       String s2 = new String("JavaRush - лучший сайт для изучения Java!");
       System.out.println(s1.equals(s2));
   }
}
Вывод в консоль:

true
Интересно. Мы точно знаем, что s1 и s2 указывают на разные области в памяти. Но, тем не менее, метод equals() говорит, что они равны. Почему? Помнишь, выше мы говорили о том, что метод equals() можно переопределить в своем классе, чтобы он сравнивал объекты так, как тебе нужно? С классом String так и поступили. У него есть переопределенный метод equals(). И сравнивает он не ссылки, а именно последовательность символов в строках. И если текст в строках одинаковый, неважно, как они были созданы и где хранятся: в пуле строк, или в отдельной области памяти. Результатом сравнения будет true. Кстати, Java позволяет корректно сравнивать строки без учета регистра. В обычной ситуации, если написать одну из строк, например, капсом, то результатом сравнения будет false:
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush - лучший сайт для изучения Java!";
       String s2 = new String("JAVARUSH - ЛУЧШИЙ САЙТ ДЛЯ ИЗУЧЕНИЯ JAVA!");
       System.out.println(s1.equals(s2));
   }
}
Вывод в консоль:

false
Для этого случая в классе String имеется метод equalsIgnoreCase(). Если в твоем сравнении главным является именно последовательность конкретных символов, а не их регистр, можно применить его. Например, это будет полезно при сравнении двух почтовых адресов:
public class Main {

   public static void main(String[] args) {

       String address1 = "г. Москва, ул. Академика Королева, дом 12";
       String address2 = new String("Г. МОСКВА, УЛ. АКАДЕМИКА КОРОЛЕВА, ДОМ 12");
       System.out.println(address1.equalsIgnoreCase(address2));
   }
}
В данном случае очевидно, что речь идет об одном и том же адресе, поэтому использование метода equalsIgnoreCase() будет верным решением.

Метод String.intern()

У класса String есть еще один хитрый метод — intern(); Метод intern() напрямую работает со String Pool’ом. Если ты вызываешь метод intern() у какой-то строки, он:
  • Смотрит, есть ли строка с таким текстом в пуле строк
  • Если есть — возвращает ссылку на нее в пуле
  • Если же нет — помещает строку с этим текстом в пул строк и возвращает ссылку на нее.
Применив метод intern() к ссылке на строку, которая создавалась через new, мы можем сравнивать ее со ссылкой на строку из String Pool’a через оператор ==.
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush - лучший сайт для изучения Java!";
       String s2 = new String("JavaRush - лучший сайт для изучения Java!");
       System.out.println(s1 == s2.intern());
   }
}
Вывод в консоль:

true
Раньше, когда мы сравнивали их без intern(), результат был равен false. Теперь же метод intern() проверил, есть ли строка с текстом "JavaRush — лучший сайт для изучения Java!" в пуле строк. Разумеется, она там есть: мы создали ее, когда написали
String s1 = "JavaRush — лучший сайт для изучения Java!";
Была проведена проверка, что ссылка s1 и ссылка, возвращенная методом s2.intern() указывают на одну область в памяти, и, конечно, так оно и есть:) Подводя итоги, запомни и используй главное правило: Для сравнения строк ВСЕГДА используй метод equals()! Сравнивая строки, ты почти всегда имеешь в виду сравнение их текста, а не ссылок, областей памяти и прочего. Метод equals() делает именно то, что тебе нужно. Вот тебе несколько ссылок для самостоятельного изучения:
Комментарии (291)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Aleksei Sotnikov Team Lead
31 марта, 14:40
Мне одному кажется, что в Уровне 3 Лекции 9, было более чем предостаточно информации на эту тему? Но большое спасибо автору за старание!🙏 И в самом конце статьи узнал что-то новое.
Dmitry Sokolov
Уровень 9
17 марта, 11:29
Тоже не понял почему автор переопределяет метод equals() и почему у него первый раз вывело false при сравнении "днк". В целом материал статьи полностью копирует информацию из лекции Уровень 3 Лекция 9. Единственное что в кратце автор написал про intern. 🤷‍♂️
Сергей Дубакин
Уровень 13
Expert
17 марта, 09:46
Молодец. Отличная статья 👍👍👍
Светлана Мягкова QA Automation Engineer в Гаскар групп
23 января, 11:11
👍
Евгений
Уровень 6
Expert
18 января, 04:54
Не совсем понятно. В начале автор говорит, что "Внутри метода equals() класса Object лежит то же самое сравнение ссылок, ==. " - поэтому сравнивая строки он получает false. Потом он переопределяет метод. А далее пишет, что метод equals сравнивает не адреса в памяти, поэтому следует использоваиь его. Как, блин, так? о_О
Viacheslav B. System Administrator
20 января, 19:59
В классе String уже есть переопределенный метод equals(). который уже сравнивает не ссылки, а именно последовательность символов в строках.
Anonymous #3346123
Уровень 14
5 апреля, 15:29
Метод equals() по определению, (то есть по умолчанию) сравнивает ссылки объектов. Сила методов в том, что их можно переопределять, то есть заставлять их делать нечто иное. Автор предлагает методу сравнивать не ссылки, а внутренности объектов dnaCode : public boolean equals(Man man) { return this.dnaCode == man.dnaCode; } // в результате true ЗЫ. Такой же результат можно получить и без метода equals(), если напрямую действовать System.out.println(man1.dnaCode == man2.dnaCode);
Anonymous #3362121
Уровень 3
16 октября 2023, 07:42
Для прояснения ситуации для себя (не всё было разобрано в лекции) - попробовал выполнить такой код: String s1 = new String("JavaRush - лучший сайт для изучения Java!"); String s2 = "JavaRush - лучший сайт для изучения Java!"; System.out.println(s1 == s2.intern()); System.out.println(s2 == s1.intern()); В первом случае будет false, во втором true. Это просто взрыв мозга, я не понял, почему. С equals в обоих случаях true.
Mgx
Уровень 16
16 октября 2023, 21:16
Программа выполняется последовательно. В первом случае, ты проверяешь ссылку на объект(на s2), который ещё не был создан и не был добавлен в String pool. А во втором случае, объект s1, уже был создан и поэтому он возвращает true
Anton Nikitin
Уровень 9
19 октября 2023, 20:56
Правильно, ведь в первом случае ты пытаешься строку из пула (s2) найти в пуле и сравнить с другим объектом (s1), и они не будут равны, а во втором случае, ты уже пытаешься строку не из пула (s1) найти в пуле и сравнить c s2 (строкой из пула), там всё получается как и в примере.
Andrey
Уровень 21
30 ноября 2023, 15:41
Первое, что нужно помнить, что оператор == сравнивает ссылки. В первом случае
s1 == s2.intern()
- возьмет содержимое строки s2, - посмотрит, закэширована ли она (посмотрит есть ли такое в пуле), - найдет совпадение - вернет ссылку на s2 ЗАТЕМ, - сравнит её со ссылкой s1 (которая не кэшируется из-за создания оператором new) - false во втором случае
s2 == s1.intern()
- возьмет содержимое строки s1, - посмотрит, есть ли такое в пуле, - найдет совпадение - вернет ссылку на s1 ЗАТЕМ - сравнит её со ссылкой s2 (строкой, которая была создана обычным способом и поэтому закэширована) - true
Rufat
Уровень 15
10 марта, 12:06
Объясни, почему, когда через new создается, то не кэшируется, а когда без new, то кэшируется?
Бромгексин
Уровень 16
31 марта, 16:03
простые особенности язык в этом не нужно разбираться
De Stroy
Уровень 10
4 апреля, 23:12
Это сделано, чтобы поддержать баланс между эффективным использованием памяти (через использование пула, адреса будут совпадать) и возможностью объекта иметь свой собственный адрес (при использовании new). Java даёт возможность выбора программисту, исходя из задач.
UmkaSHINE
Уровень 4
23 сентября 2023, 17:44
Я не понял, почему для сравнения строк, лучше всегда использовать .equals() ?? если intern() через ==, вроде как делает тоже самое?
Novikova Natalia
Уровень 28
9 октября 2023, 22:32
equals вроде просто самый ходовой
Anton Nikitin
Уровень 9
19 октября 2023, 20:59
выше человек ответил почему =)
Miraga
Уровень 34
23 ноября 2023, 16:27
"С классом String так и поступили. У него есть переопределенный метод equals(). И сравнивает он не ссылки, а именно последовательность символов в строках. И если текст в строках одинаковый, неважно, как они были созданы и где хранятся: в пуле строк, или в отдельной области памяти." А intern() обычно используется для уменьшения накладных расходов при работе с большим количеством строк с одинаковым содержимым.
ThisKRuT
Уровень 12
8 сентября 2023, 04:15
В каком случае в реальных проектах может использоваться intern() ?
crochet
Уровень 29
17 сентября 2023, 16:18
Суть этого метода - экономия памяти за счет времени исполнения. Другими словами, как я понимаю, когда действительно потребуется этот метод, программист настолько преисполнится в своем познании Джава, что увидит это невооруженным взглядом, и будет способен аргументировать его необходимость. Именно поэтому в конце лекции написано "ВСЕГДА используй метод equals". Как натолкнешься на реальную необходимость этого метода - сможешь по фактам расписать его необходимость :)
Roman Kibenko
Уровень 9
25 августа 2023, 04:45
вроде все понятно, но есть вопрос, почему String text = "Привет"; String text1 = "Привет"; String s1 = text.toUpperCase(); String s2 = text.toUpperCase(); System.out.println(text == text1); System.out.println(text == s1); System.out.println(s1 == s2); то: true false false почему System.out.println(s1 == s2); выдает false. ведь у него такой же метод, как и у s1? как я понял - использование toUpperCase() это также как и использование оператора new и создает новую область в StringPool?
Alexander Potapov
Уровень 9
2 сентября 2023, 19:49
Да, когда вы используете методы, такие как toUpperCase(), для работы со строками, это приводит к созданию новой строки, в которой все символы преобразованы к верхнему регистру. Результат этого метода не берется из пула строк. Вместо этого, создается новый объект в памяти, в котором сохраняется измененная строка. Таким образом метод toUpperCase() создает новый объект строки, и этот новый объект не добавляется в пул строк.
Roman Kibenko
Уровень 9
3 сентября 2023, 18:10
спасибо 👍
Kaz
Уровень 17
18 сентября 2023, 20:57
Точно так же задавался этим вопросом. Моя проблема была в том, что я сначала воспринимал все существующие строки находящимся в String Pool. Как выяснилось из этой лекции в String pool попадают только те строки, которые создаются присваиванием переменной типа String какого-то текста в кавычках.
String text = "Привет";//попало в пул
String text1 = "Привет";//попало в пул
String s1 = text.toUpperCase();//не попало в пул, а отдельный объект в памяти
String s2 = text.toUpperCase();//не попало в пул, а еще один отдельный объект в памяти
s1 == s2 - false именно потому, что сравниваются ссылки ведущие к разным объектам
M K
Уровень 32
2 мая 2023, 12:16
Вторая половина понятна. Не понятно почему в самом начале тогда когда мы сравнивали и ссылки строк и через equals их сравнивали значение было false? Если дальше мы делаем то же самое и сам автор пишет: Каждый раз, когда ты пишешь String = “........”, программа проверяет, есть ли строка с таким текстом в пуле строк. Если есть — новая создана не будет. И новая ссылка будет указывать на тот же адрес в пуле строк, где эта строка хранится. Но в первом коде это не работало. Почему? Получается автор сначала пишет одно, а потом прямо противоположное. Объясните пожалуйста как так получается.
Nekital360
Уровень 38
9 мая 2023, 15:42
про какое начало речь? где автор создал класс и сравнивал объекты?
M K
Уровень 32
10 мая 2023, 13:48
M K
Уровень 32
10 мая 2023, 13:49
Вот тут и выше тоже
Nekital360
Уровень 38
13 мая 2023, 18:20
так там ниже написан ответ, потому что в классе Object "оригинальный" метод equals сравнивает ссылки, чтобы он сравнивал объекты надо переопределить(изменить) самому этот метод как нам надо. И где строки вроде true выводится где одинаковые ссылки s1 и s2.
Alexander Potapov
Уровень 9
2 сентября 2023, 19:56
Так вот же автор в статье пишет: "... У него (класса String) есть переопределенный метод equals(). И сравнивает он не ссылки, а именно последовательность символов в строках. И если текст в строках одинаковый, неважно, как они были созданы и где хранятся: в пуле строк, или в отдельной области памяти. Результатом сравнения будет true." Т.е. у него уже прописано переодпредение, в отличие от примера в начале (class Man).