Когда "" == s ложно, но "" .equals (s) верно - PullRequest
33 голосов
/ 10 июля 2009

EDIT Спасибо за быстрые ответы. Пожалуйста, посмотрите, каков реальный вопрос. На этот раз я выделил это жирным шрифтом.

Я понимаю разницу между == и .equals. Так что это не мой вопрос (на самом деле я добавил для этого некоторый контекст)

<ч />

Я выполняю проверку ниже для пустых строк:

if( "" == value ) { 
    // is empty string 
} 

В прошлом при извлечении значений из базы данных или десериализации объектов из другого узла этот тест не удался , потому что два экземпляра строки действительно были различными ссылками на объекты, хотя и содержали те же данные.

Так что исправление для этих ситуаций было

if( "".equals( value ) ) {
   // which returns true for all the empty strings
}

Я в порядке с этим. Это ясно понято.

Сегодня это повторилось, но меня это озадачило, потому что на этот раз это очень маленькое автономное приложение , которое вообще не использует сеть , поэтому новая строка не извлекается из базы данных и не десериализируется с другого узла.

Итак, вопрос:


При каких ДРУГИХ обстоятельствах:

"" == value // yields false 

и

"".equals( value ) // yields true

Для локального автономного приложения?

Я почти уверен, что new String () не используется в коде.

И единственный способ, которым ссылка на строку может быть "", это потому, что она назначается "" непосредственно в коде (или это то, что я думал), как в:

String a = "";
String b = a;

assert "" == b ; // this is true 

Каким-то образом (после прочтения кода у меня есть подсказка) были созданы две разные ссылки на пустые строковые объекты, я хотел бы знать как

Еще в строке jjnguys ответ:

Byte!

РЕДАКТИРОВАТЬ: Заключение

Я нашел причину.

После предложения jjnguy я смог взглянуть на код другими глазами.

Виновный метод: StringBuilder.toString ()

Новый объект String выделяется и инициализируется для хранения последовательности символов, представленной в данный момент этим объектом.

Дох! ...

    StringBuilder b = new StringBuilder("h");
    b.deleteCharAt( 0 );
    System.out.println( "" == b.toString() ); // prints false

Тайна раскрыта.

Код использует StringBuilder для работы с постоянно растущей строкой. Оказывается, в какой-то момент кто-то сделал:

 public void someAction( String string ) { 
      if( "" == string ) {
           return;
       }

       deleteBankAccount( string );
 }

и использовать

 someAction( myBuilder.toString() ); // bug introduced. 

p.s. Я читал слишком много CodingHorror в последнее время? Или почему я чувствую необходимость добавить сюда несколько забавных картинок с животными?

Ответы [ 10 ]

26 голосов
/ 10 июля 2009
String s = "";
String s2 = someUserInputVariale.toLowercase(); // where the user entered in ""

Нечто подобное может привести к тому, что s == s2 станет ложным.

Множество кодов порождают новые Strings без предоставления вызова new String().

21 голосов
/ 10 июля 2009
"" == value // yields false

и

"".equals( value ) // yields true

каждый раз, когда значение переменной value не было интернировано. Это будет иметь место, если значение вычисляется во время выполнения. См. JLS раздел 3.10.5 Строковые литералы для примера кода, иллюстрирующего это:

Таким образом, тестовая программа, состоящая из блока компиляции (§7.3):

package testPackage;
class Test {
    public static void main(String[] args) {
        String hello = "Hello", lo = "lo";
        System.out.print((hello == "Hello") + " ");
        System.out.print((Other.hello == hello) + " ");
        System.out.print((other.Other.hello == hello) + " ");
        System.out.print((hello == ("Hel"+"lo")) + " ");
        System.out.print((hello == ("Hel"+lo)) + " ");
        System.out.println(hello == ("Hel"+lo).intern());
    }
}
class Other { static String hello = "Hello"; }

и блок компиляции:

package other;
public class Other { static String hello = "Hello"; }

производит вывод:

true true true true false true

Этот пример иллюстрирует шесть пунктов:

  • Литеральные строки в одном и том же классе (§8) в одном и том же пакете (§7) представляют ссылки на один и тот же объект String (§4.3.1).
  • Литеральные строки в разных классах в одном и том же пакете представляют ссылки на один и тот же объект String.
  • Литеральные строки в разных классах в разных пакетах также представляют ссылки на один и тот же объект String.
  • Строки, вычисленные с помощью константных выражений (§15.28), вычисляются во время компиляции, а затем обрабатываются как литералы.
  • Строки, вычисленные во время выполнения, создаются заново и поэтому отличаются.
  • Результатом явного интернирования вычисляемой строки является та же строка, что и любой ранее существовавшей литеральной строкой с таким же содержимым.
10 голосов
/ 10 июля 2009

Если вы можете взять книгу Джошуа Блоха и Нила Гафтера Java Puzzlers и взглянуть на головоломку 13 «Скотный двор» ... у него есть отличный совет по этому вопросу. Я собираюсь скопировать соответствующий текст:

"Возможно, вы знаете, что константы времени компиляции типа String являются интернированными [JLS 15.28]. Другими словами любые два константных выражения типа String, которые обозначить одну и ту же последовательность символов, которые представлены одинаковыми ссылками на объекты ... Ваш код редко, если вообще когда-либо, должен зависеть от интернирования строковых констант. Интернирование было разработано исключительно для уменьшения объема памяти виртуальной машины, не как инструмент для программистов ... При сравнении ссылок на объекты вы должны использовать метод equals вместо оператора == , если вам не нужно сравнивать идентичность объекта, а не значение. "

Это из вышеупомянутой ссылки, которую я упомянул ... стр. 30 - 31 в моей книге.

7 голосов
/ 10 июля 2009

Ожидаете ли вы, что "abcde".substring(1,2) и "zbcdefgh".substring(1,2) приведут к одному и тому же объекту String?

Они оба дают "одинаковые" подстроки, извлеченные из двух разных строк, но кажется вполне разумным, что они являются разными объектами, поэтому == видит их как разные.

Теперь рассмотрим, когда подстрока имеет длину 0, substring(1, 1). Он возвращает строку нулевой длины, но неудивительно, что "abcde".substring(1,1) отличается от "zbcdefgh".substring(1,2) и, следовательно, по крайней мере один из них отличается от "".

3 голосов
/ 10 июля 2009

Как я понимаю, при компиляции Java-кода в байт-код или при запуске программы в большинстве случаев для сохранения памяти ссылки на один и тот же объект будут ссылаться на один и тот же объект. Так что иногда вам сходит с рук == сравнения строк. Но это оптимизация компилятора, на которую нельзя положиться.

Но иногда случается, что компилятор решает не выполнять эту оптимизацию, или у программы нет возможности увидеть, что строки совпадают, и внезапно проверка завершается неудачно, поскольку вы полагаетесь на некоторую базовую оптимизацию voodoo. это зависит от реализации используемой вами jvm и так далее.

Так что использование равных - это всегда хорошо. Для пустых строк есть и другие возможности, такие как сравнение с длиной == 0 или, если вам не нужна обратная совместимость, есть string.empty ().

2 голосов
/ 10 июля 2009

Вы должны попробовать рассмотреть String.length() == 0.

0 голосов
/ 11 июля 2009

Если вы используете поиск по коду Google, вы можете найти множество мест, где люди делают такую ​​же ошибку: Google для файла: .java \ = \ = \ \ "\" Конечно, это может быть правильная идиома в тщательно контролируемых обстоятельствах, но обычно это просто ошибка.

0 голосов
/ 10 июля 2009

В javadoc для String.intern() есть хороший комментарий к == против .equals().

В документации также уточняется, что каждый строковый литерал имеет значение intern 'd.

public String intern ()

Возвращает каноническое представление для строкового объекта.

Пул строк, изначально пустой, поддерживается частным образом классом String.

При вызове метода intern, если пул уже содержит строку равна этому объекту String как определяется равными (объект) метод, то строка из пула возвращается В противном случае эта строка объект добавляется в пул и ссылка на этот объект String вернулся.

Отсюда следует, что для любых двух строк s и t, s.intern () == t.intern () истина тогда и только тогда, когда s.equals (t) правда.

Все литеральные и строковые значения константные выражения интернированы. Строковые литералы определены в §3.10.5 спецификации языка Java

Возвращает: строка с таким же содержимое как эта строка, но гарантированно из пула уникальных строки.

0 голосов
/ 10 июля 2009

Проверьте эту ссылку: http://mindprod.com/jgloss/string.html#COMPARISON в отличном Canadian Mind Products Java & Internet Glossary . Стоит закладки.

0 голосов
/ 10 июля 2009

Почему бы не использовать:

if (value != null && value.length == 0) {
    // do stuff (above could be "== null ||"
}

Вы должны использовать equals(), поскольку == для объектов сравнивает ссылки, т. Е. Являются ли они одним и тем же объектом. Хотя во время компиляции Java находит идентичные строки и заставляет их использовать одну и ту же ссылку (строки являются неизменяемыми), во время выполнения легко создать пустые строки с разными ссылками, где == не удается для вашего типичного намерения equals().

...