"==" в случае конкатенации строк в Java
String a = "devender";
String b = "devender";
String c = "dev";
String d = "dev" + "ender";
String e = c + "ender";
System.out.println(a == b); //case 1: o/p true
System.out.println(a == d); //case 2: o/p true
System.out.println(a == e); //case 3: o/p false
a и b оба указывают на тот же строковый литерал в пуле строковых констант. Итак, true
в случае 1
String d = "dev" + "ender";
должен быть внутренне использован что-то вроде -
String d = new StringBuilder().append("dev").append("ender").toString();
Как a и d указывают на одну и ту же ссылку, а не a и e?
Ответы
Ответ 1
Четыре вещи продолжаются:
-
(Вы точно это знаете, но для lurkers) ==
проверяет, указывают ли переменные на тот же String
объект, а не на эквивалентные строки. Поэтому даже если x
равно "foo"
, а y
также "foo"
, x == y
может быть истинным или ложным, в зависимости от того, относятся ли теги x
и y
к одному и тому же объекту String
или к другим, Поэтому для сравнения строк для эквивалентности мы используем equals
, а не ==
. Все из следующего объясняется тем, почему ==
иногда истинно, это не предложение использовать ==
для сравнения строк.: -)
-
Эквивалентные строковые константы (строки, которые знает компилятор, являются константами в соответствии с различными правилами в JLS) в одном классе, ссылаются на одну и ту же строку компилятором (который также перечисляет их в классе "постоянный пул" ). Вот почему a == b
истинно.
-
Когда класс загружается, каждая из его строковых констант автоматически interned — пул строк JVM проверяется на эквивалентную строку, и если он найден, используется объект String
(если нет, то новый объект String
для новой константы добавляется в пул). Поэтому даже если x
является строковой константой, инициализированной в классе Foo
, а y
является строковой константой, инициализированной в классе Bar
, они будут ==
друг друга.
Баллы 2 и 3 выше частично описаны JLS§3.10.5. (Бит о пуле констант класса - это немного детализация реализации, поэтому ссылка на спецификацию JVM ранее, JLS просто говорит о интернировании.)
-
Компилятор выполняет конкатенацию строк, если он имеет дело с постоянными значениями, поэтому
String d = "dev" + "ender";
скомпилирован в
String d = "devender";
и "devender"
- это строковая константа, которую компилятор и JVM применяют к пунктам 2 и 3 выше. Например, no StringBuilder
используется, конкатенация происходит во время компиляции, а не во время выполнения. Это описано в JLS§15.28 - Константные выражения. Таким образом, a == d
истинно по той же причине a == b
имеет значение true: они относятся к одной и той же константной строке, поэтому компилятор гарантировал, что они ссылаются на одну и ту же строку в пуле констант класса.
Компилятор не может этого сделать, если какой-либо из операндов не является константой, поэтому он не может сделать это с помощью
String e = c + "ender";
... хотя анализ кода может легко показать, что значение c
будет, безусловно, "dev"
, и, таким образом, e
будет определенно "devender"
. Спецификация содержит только компилятор с конкатенацией с постоянными значениями. Поэтому, поскольку компилятор не может этого сделать, он выдает код StringBuilder
, на который вы ссылались, и эта работа выполняется во время выполнения, создавая новый объект String
. Эта строка не выполняется автоматически, поэтому e
заканчивается ссылкой на другой объект String
, чем a
, и поэтому a == e
является ложным.
Обратите внимание, что как сказал Vinod, если вы объявили c
как final
:
final String c = "dev";
Тогда это будет постоянная переменная (да, их действительно называют), и поэтому применим §15.28, и компилятор будет включить
String e = c + "ender";
в
String e = "devender";
и a == e
также верны.
Просто повторить: ничто из этого не означает, что мы должны использовать ==
для сравнения строк для эквивалентности.:-) То, что equals
для.
Ответ 2
Компилятор делает большую оптимизацию под капотом.
String d = "dev" + "ender";
Здесь компилятор заменит "dev" + "ender"
на "devender"
при компиляции программы. Если вы добавляете 2 литерала (это относится как к примитивам, так и к строкам), компилятор делает эту оптимизацию.
Код Java:
String d = "dev" + "ender";
Байт-код:
0: ldc #16 // String devender
Переход к частному случаю:
final String c = "dev"; // mark this as final
String e = c + "ender";
Создание c
final сделает String постоянной времени компиляции. Компилятор поймет, что значение c
не может измениться и, следовательно, заменит все события c
на значение "dev" при компиляции, таким образом e
будет разрешено во время самого компиляции.
Ответ 3
Как вы сказали внутренне, последняя конкатенация выполняется с чем-то похожим на
String e = new StringBuilder().append(c).append("ender").toString();
реализация toString()
StringBuilder
создает новую строку. Вот реализация.
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
Сравнение строк с использованием ==
вместо .equals()
возвращает true
только в том случае, если обе строки одинаковы. В этом случае они не совпадают, поскольку вторая строка создается как новый объект типа String
.
Другие конкатенации выполняются непосредственно компилятором, поэтому новая String не создается.
Ответ 4
"dev" + "ender"
- это константное выражение для оценки времени компиляции: оба аргумента являются строковыми литералами. Следовательно, выражение "devender"
.
То же самое нельзя сказать о c + "ender"
: некоторые обстоятельства (например, некоторый код, запущенный в другом потоке) могут привести к тому, что значение c
будет установлено на другое значение. Квалификация c
как final
устраняет эту возможность, и в этом случае e
также ссылается на тот же объект, что и a
.
So a
, b
и d
все относятся к одному и тому же объекту.
Ответ 5
Разница между d
и e
заключается в том, что при конкатенации строковых литералов конкатенация выполняется во время компиляции. Компилятор Java рассматривает выражение "dev" + "ender"
так же, как выражение "devender"
, создавая один и тот же литерал во время компиляции. Поскольку все литералы String
получают интернированный, d
, который является результатом "dev" + "ender"
, также заканчивается ссылкой на тот же объект, что и a
и b
"devender"
.
Выражение для e
, которое c + "ender"
, оценивается во время выполнения. Несмотря на то, что он создает ту же строку, этот факт не используется компилятором. Поэтому возникает другой объект String
, что приводит к неудачному сравнению с ==
.
Ответ 6
Строка d = "dev" + "ender"; константа + константа, d всегда является константой (одна и та же), поэтому (a == d) истинно;
String e = c + "ender"; variable + constant, результат 'e' является переменной, он будет использовать StringBuilder внутри и создать новую ссылку.
Ответ 7
Имейте в виду, что Java содержит пул всех строковых литералов, найденных в программе, используемых для сопоставления целей среди других, поэтому любая связанная строка буквальное приведенная выше приведет к тому же объекту, к тому же строковый литерал. Вы можете проверить эту полезную статью для более подробной информации.
С другой стороны, конкатенация объекта String
и литерала (case c + "ender"
) приведет к созданию объекта as StringBuilder
во время выполнения, отличного от литералов, найденных в пуле.