Использование оператора == для сравнения строк в Java неправильно и может привести к неожиданным результатам в том смысле, что иногда при сравнении одинаковых строк с помощью == мы будем получать true, а иногда false. Для корректного и предсказуемого сравнения строк в Java необходимо пользоваться методом equals(), который может быть вызван на любой переменной, литерале или ссылке типа String (и вообще на любом объекте, так как наследуется из класса Oject): "Hello".equals(new String("Hello"));//true
Оператор ==
Оператор == сравнивает значения своих операндов. Применительно к примитивным типам данных это означает сравнение непосредственных значений (чисел, значений true/false) содержащихся в переменных или литералах: double d = 1.0;if (d == 1.0) {} В данном примере сравнивается значение переменной d и литерала 1.0. Разумеется, эти два значения равны и мы войдём внутрь if'а. Это же справедливо и для ссылочных типов. Однако, загвоздка в том, что значением ссылочного типа является ссылка, то есть адрес в памяти машины, а уже по этому адресу располагается объект. Создадим две ссылочные переменные: String str1 = new String("Hello");String str2 = new String("Hello"); Хотя мы и будем оперировать объектом "Hello" с помощью переменной str1, но значение этой переменной не "Hello". Значение этой переменной может быть таким: 4b67cf4d. То есть это какой-то адрес в памяти (на самом деле, это хэшкод объекта, но для простоты будем считать его адресом в памяти), в котором располагается конкретная строка. str1 -> 4b67cf4d -> "Hello" Таким образом значения переменных str1 и str2 разные. Значение str1, например, равно 4b67cf4d, а str2 -- 7ea987ac. Соответственно значение этих ссылочных переменных разные. А тот факт, что по этим разным адресам лежат одинаковые строки, для оператора == никакого значения не имеет, так как он не умеет ходить по ссылкам. Оператор ==, применённый к двум ссылочным типам вернёт true только в том случае, если переменные будут ссылаться на один и тот же объект, то есть их значением будет один и тот же адрес памяти машины.
Метод equals()
Метод equals() имеется у класса Object и, как следствие, наследуется всеми остальными объектами. По умолчанию он выглядит так: public boolean equals(Object obj) { return (this == obj);} То есть делает то же самое, что и оператор ==, а именно сравнивает значения переменных. Здесь эти переменные всегда будут ссылочного типа, а значит equals() сравнит значения адресов, безотносительно содержания конкретных объектов. Этот метод создан для того, чтобы все классы-потомки его переопределяли. В переопределённой версии метода предполагается сравнивать фактическое состояние непосредственных объектов, на которые указывают ссылки, а не просто адреса. Класс String, разумеется, переопределяет этот метод и в переопределённой версии сравнивает непосредственно строки, расположенные по адресам на которые ссылаются переменные (и/или строковые литералы). Таким образом метод equals() вернёт true, если фактическое содержание строк будет одинаковым с точки зрения содержащихся в них символов, безотносительно того, одинаковы или различны ссылки на эти объекты: String str1 = new String("Hello");String str2 = new String("Hello");System.out.println(str1.equals(str2)); Последнее выражение выведет true. Хотя совершенно очевидно, что есть два разных строковых объекта, содержащих символы 'H'+'e'+'l'+'l'+'o'. С точки зрения метода equals() это объекты "эквивалентны" друг другу, а значит нужно вернуть true. Во избежание проблемы с NullPointerException рекомендуется вызывать метод equals() на объекте, про который мы можем быть уверены, что он не null. Часто таким объектом является литерал, поэтому инструкции вида boolean isHello = "hello".equals(str); являются обычным делом, хотя интуитивно хочется сравнивать переменную со строкой: str.equals("hello"), а не наоборот. Но первый вариант является null-безопасным.
Проблема разного поведения == в разных ситуациях
Java для своих программ поддерживает так называемый пул строк. Так, строковые объекты могут быть записаны непосредственно в текст программы с помощью строковых литералов в двойных кавычках: String str1 = "Hello"; В данном примере "Hello" -- это литерал объекта строки. Подобные литералы помещаются в пул строк ещё на этапе компиляции программы и во время выполнения Java-машина не создаёт новые строковые объекты, а пользуется этим пулом. Например: String str1 = "Hello";String str2 = "Hello"; В данном случае обе переменные будут ссылаться на один и тот же объект в памяти. При компиляции программы в первой строчке компилятор обнаружит строковый литерал "Hello" и поместит его в пул строк. На второй строчке компилятор снова обнаружит литерал "Hello", но, поскольку в пуле такая строка уже есть, то компилятор просто заставит обе переменные ссылаться на один и тот же объект в пуле. Таким образом: String str1 = "Hello";String str2 = "Hello";System.out.println(str1 == str2); выведет true, так как фактически str1 и str2 ссылаются на один и тот же адрес в памяти (в пуле строк). Однако, компиляция строковых литералов может оказаться более сложным процессом, чем видится на первый взгляд, в силу различных дополнительных оптимизаций, в том числе обработки констант времени компиляции. Например: String str1 = "Hello";String str2 = "He" + "llo";System.out.println(str1 == str2); Сравнение str1 == str2 вернёт true, что мы увидим на консоли, и вот почему. Может показаться, что раз в программе использовано три строковых литерала: "Hello", "He" и "llo", то все три будут добавлены в пул строк. А значение переменной str2 будет вычислено во время выполнения. В результате суммирования строк появится новый объект, которого ни в каком пуле быть не может, а значит переменные должны ссылаться на разные объекты: str1 на "Hello" в пуле строк, а str2 на новосозданный "Hello" где-то в другом месте. Но эта штука работает по-другому. Компилятор видит выражение String str2 = "He" + "llo"; следующим образом: "He" + "llo" могут быть "суммированы" уже на этапе компиляции, что и происходит. В результате в "сумме" получается строка "Hello", которая, как оказывается, уже есть в пуле. Таким образом можно заставить переменную str2 просто ссылаться на "Hello" в пуле и на работу программы это никак не повлияет, а вычислительные ресурсы сэкономить позволит. Также важно понимать, что в данном случае == по-прежнему не сравнивает строки. Сравниваются только адреса ссылок. Кроме того, в классе String есть метод intern(), который позволяет добавлять в пул строку, созданную во время выполнения программы. Таким образом, следующее выражение вернёт true: new String("test").intern() == "test" Часть выражение, расположенная слева от == в итоге будет также указывать на строку "test" в пуле, так как к вновь созданному объекту применён метод intern(). Разумеется литерал "test" также указывает на тот же адрес в пуле и == вернёт true.
Скорость работы equals()
Метод String.equals() работает очень оптимально. - Сначала ссылки проверяются через ==. И если ссылки указывают на один и тот же строковый объект, то, безусловно, сразу возвращается true. - Затем проверяется длина строк. Если строки оказываются разной длины, то сразу возвращается false; - Затем строки сравниваются символ за символом до первого расхождения. При первом же различии возвращается false. Но если строки совпадут полностью, то вернётся true. Первичная проверка с помощью == в совокупности с пулом строк делают работу со строками в Java довольно непроблемным с точки зрения производительности делом. Тем не менее, при больших объёмах данных бывает всякое. Если новых строк образуется много и сравнения происходят часто и это становится проблемой, то, стоит попробовать пользоваться методом intern().
Другие способы сравнения строк
Метод equalsIgnoreCase() позволяет сравнить строки без относительно регистра содержащихся в строках символов. Следующий код выведет true: System.out.println("HELLO".equalsIgnoreCase("hello")); Метод contentEquals() принимает в качестве параметра CharSequence или StringBuffer и позволяет сравнивать строки с объектами этих типов без лишних преобразований к String: System.out.println("hello".contentEquals("hello")); Метод compareTo() вернёт 0, если строки окажутся одинаковыми. Тем не менее, это не тот способ, которым стоит пользоваться для этой цели.