Вы точно назвали причину, по которой использование оператора +
для объединения строк может рассматриваться как историческая ошибка проектирования. Предоставление встроенного оператора конкатенации не является неправильным, но это не должен быть оператор плюс.
Помимо путаницы в различном поведении, например, для 'a'+'b'
и ""+'a'+'b'
, оператор плюс обычно ожидается коммутативный, т. е. a + b
имеет тот же результат, что и b + a
, что не относится к конкатенации строк. Кроме того, приоритет оператора может привести к неожиданностям.
Поведение точно определено (JLS §15.18.1) :
15.18.1. Оператор конкатенации строк +
Если только одно выражение операнда имеет тип String
, то преобразование строки ( §5.1.11 ) выполняется для другого операнда для получения строки во время выполнения .
Результатом объединения строк является ссылка на объект String
, который является объединением двух строк операндов. Символы левого операнда предшествуют символам правого операнда во вновь созданной строке.
Это определение ссылается на §5.1.11 :
5.1.11. Преобразование строки
Любой тип может быть преобразован в тип String
с помощью преобразование строки .
Значение x
примитивного типа T
сначала преобразуется в ссылочное значение, как если бы оно передавалось в качестве аргумента соответствующему выражению создания экземпляра класса ( §15.9 ):
Если T
равно boolean
, то используйте new Boolean(x)
.
Если T
равно char
, тогда используйте new Character(x)
.
Если T
равно byte
, short
или int
, затем используйте new Integer(x)
.
Если T
равно long
, тогда используйте new Long(x)
.
Если T
равно float
, используйте new Float(x)
.
Если T
равно double
, используйте new Double(x)
.
Это эталонное значение затем преобразуется в тип String
путем преобразования строки.
Теперь необходимо учитывать только эталонные значения:
-
Если ссылка null
, она преобразуется в строку "null
" (четыре символа ASCII n
, u
, l
, l
).
В противном случае преобразование выполняется как при вызове метода toString
ссылочного объекта без аргументов; но если результат вызова метода toString
равен null
, тогда вместо него используется строка "null
".
(форматирование спецификации действительно "null
", а не "null"
)
Таким образом, поведение String foo = 'a' + "bee";
определено как как если бы вы написали String foo = new Character('a').toString() + "bee";
Но процитированный §15.18.1 продолжается с:
Объект String
создается вновь ( §12.5 ), если только Выражение является константным выражением ( §15.28 ).
Реализация может выбрать выполнение преобразования и объединения за один шаг, чтобы избежать создания, а затем отбрасывания промежуточного объекта String
. Чтобы повысить производительность многократной конкатенации строк, компилятор Java может использовать класс StringBuffer
или аналогичный метод для уменьшения числа промежуточных String
объектов, которые создаются путем вычисления выражения.
Для примитивных типов реализация также может оптимизировать создание объекта-обертки путем преобразования непосредственно из примитивного типа в строку.
Так что для вашего конкретного c пример, 'a' + "bee"
, фактическое поведение
String foo = 'a' + "bee";
будет
String foo = "abee";
без каких-либо дополнительных операций во время выполнения, потому что это константа времени компиляции .
Если один из операндов не является константой времени компиляции, как
char c = 'a';
String foo = c + "bee";
Оптимизированный вариант, используемый большинством, если не всеми компиляторами от Java 5 до Java 8 (включительно), это
char c = 'a';
String foo = new StringBuilder().append(c).append("bee").toString();
См. Также этот ответ . Начиная с Java 9, будет использоваться другой подход .
Результирующее поведение всегда будет таким, как указано.