Как сравнивать строки в Java
Сравнение строк Java с помощью equals, equalsIgnoreCase, compareTo и частые ошибки с ==.
Сравнение двух значений String в Java — одна из первых вещей, которую начинающий делает неправильно, потому что оператор == тихо сравнивает ссылки, а не содержимое. В этой главе рассматриваются правильные, идиоматические способы проверки строк на равенство и упорядочение — equals, equalsIgnoreCase, compareTo и null-безопасный Objects.equals — и объясняется, когда каждый из них является подходящим инструментом.
Об основной причине, по которой одинаковые литералы могут быть ==, см. пул строк; более широкий обзор методов сравнения — в статье сравнение строк Java.
Равенство содержимого с помощью equals
Чтобы проверить, содержат ли две строки одинаковые символы, вызовите equals:
String a = "hello";
String b = new String("hello");
boolean same = a.equals(b); // true — same charactersequals обходит две последовательности символов и возвращает true только тогда, когда они имеют одинаковую длину и одинаковые символы в одинаковом порядке. Это метод, который нужен вам в 99% случаев, когда вы спрашиваете «содержат ли эти две строки одинаковый текст?».
Распространённый защитный приём — вызывать equals на строковом литерале, чтобы получатель никогда не был null:
if ("yes".equals(userInput)) { ... } // safe even if userInput is nullПочему == — классическая ловушка
== вообще не смотрит на символы — он спрашивает «являются ли эти объекты одним и тем же объектом в памяти?». Поскольку компилятор интернирует строковые литералы (хранит одну общую копию в пуле), два одинаковых литерала могут оказаться ==, что вводит людей в заблуждение, будто == сравнивает текст. Это не так: как только строка получена через new, из пользовательского ввода или конкатенации во время выполнения, == возвращает false даже для идентичного текста.
| Выражение | Результат | Почему |
|---|---|---|
"java" == "java" | true | Оба ссылаются на один и тот же интернированный литерал |
"java" == new String("java") | false | new создаёт отдельный объект |
"java".equals(new String("java")) | true | Сравнивает содержимое, а не идентичность |
Практическое правило: никогда не используйте == для сравнения строк. Используйте equals.
Сравнение без учёта регистра с equalsIgnoreCase
Когда регистр не важен — имена пользователей, расширения файлов, имена заголовков — используйте equalsIgnoreCase:
"Java".equalsIgnoreCase("JAVA"); // trueМетод применяет ту же посимвольную логику, что и equals, после приведения регистра, поэтому "README".equalsIgnoreCase("readme") возвращает true.
Упорядочение с помощью compareTo
Чтобы сортировать или ранжировать строки, а не просто проверять равенство, используйте compareTo, который возвращает знак лексикографической (словарной) разности:
"apple".compareTo("banana"); // negative — apple comes first
"apple".compareTo("apple"); // 0 — equal
"banana".compareTo("apple"); // positive — banana comes laterОтрицательный результат означает, что получатель сортируется перед аргументом, 0 означает равенство, положительный — после. Существует вариант compareToIgnoreCase, а также String.CASE_INSENSITIVE_ORDER для использования в качестве Comparator. Обратите внимание, что сортировка по умолчанию производится по кодовым позициям Unicode, поэтому все заглавные буквы (A–Z) сортируются перед строчными.
Наглядный пример
Эта программа демонстрирует все подходы рядом, чтобы вы могли точно увидеть поведение каждого из них.
Что следует взять из этого запуска:
a == bвыводитfalse, хотя обе строки содержат «java», потому чтоnew String("java")— это отдельный объект — доказательство того, что==сравнивает идентичность, а не текст.a.equals(b)выводитtrue:equalsсмотрит на символы, что почти всегда и подразумевается под «одинаковой строкой».a == cвыводитtrueтолько потому, что оба являются литералами, разделяющими один интернированный объект — именно эта случайная удача заманивает новичков в ошибку с==.equalsIgnoreCaseвыводитtrueдля"Java"и"JAVA", аcompareToвозвращает0для одинакового текста и ненулевой знак (-1для apple перед banana), когда они отличаются.Objects.equals(null, "x")выводитfalseвместо исключения, а отсортированный массив выводится как[Cherry, apple, banana]— заглавнаяC(кодовая позиция 67) сортируется перед строчными буквами (кодовая позиция 97+).
Какой метод использовать?
Выбирайте метод сравнения в зависимости от вопроса, на который вы на самом деле отвечаете:
| Вы хотите знать… | Используйте | Примечания |
|---|---|---|
| Является ли текст точно равным? | a.equals(b) | Выбор по умолчанию для равенства |
| Равны ли строки без учёта регистра? | a.equalsIgnoreCase(b) | Сначала приводит регистр |
Равны ли, но одна из сторон может быть null? | Objects.equals(a, b) | Возвращает true для двух null, никогда не выбрасывает исключение |
| Каков их порядок сортировки? | a.compareTo(b) | Отрицательный / 0 / положительный |
| Порядок сортировки без учёта регистра? | a.compareToIgnoreCase(b) | Или String.CASE_INSENSITIVE_ORDER как Comparator |
Полезная привычка: когда нужна только проверка равенства, предпочитайте equals (или Objects.equals, когда возможен null) и никогда не используйте compareTo(...) == 0, который выполняет строго больше работы. Чтобы изучить методы, на которых это основано, см. методы строк Java и класс String.