Как класс String отменяет оператор +?

129

Почему в Java вы можете добавлять строки с помощью оператора +, если String - это класс? В String.javaкоде я не нашел реализации для этого оператора. Нарушает ли эта концепция объектную ориентацию?

Pooya
источник
21
+Оператор Plus ( ) - это языковая функция Java.
adatapost
18
Это все волшебство компилятора. Вы не можете выполнять перегрузку операторов в Java.
Lion
18
Я думаю, что странно то, что String реализована как библиотека вне языка (в java.lang.String), но имеет конкретную поддержку внутри языка. Это неправильно.
13рен
@ 13ren ты ошибаешься. Строка является частью класса java.lang, который обозначает язык - язык java !!! Все классы в этом пакете особенные. Обратите внимание, что вам не нужно импортировать java.lang для их использования.

Ответы:

156

Давайте посмотрим на следующие простые выражения в Java

int x=15;
String temp="x = "+x;

Компилятор внутренне преобразует его "x = "+x;в StringBuilderи использует .append(int)для «добавления» целого числа к строке.

5.1.11. Преобразование строк

Любой тип может быть преобразован в тип String путем преобразования строки.

Значение x примитивного типа T сначала преобразуется в ссылочное значение, как если бы оно передавалось в качестве аргумента соответствующему выражению создания экземпляра класса (§15.9):

  • Если T является логическим, используйте new Boolean (x).
  • Если T - char, используйте новый символ (x).
  • Если T является байтовым, коротким или целым числом, используйте новое целое число (x).
  • Если T длинный, используйте новый Long (x).
  • Если T - float, используйте новый Float (x).
  • Если T - double, используйте новый Double (x).

Это ссылочное значение затем преобразуется в тип String путем преобразования строки.

Теперь нужно учитывать только справочные значения:

  • Если ссылка пустая, она преобразуется в строку «null» (четыре символа ASCII n, u, l, l).
  • В противном случае преобразование выполняется, как если бы путем вызова метода toString указанного объекта без аргументов; но если результатом вызова метода toString является null, вместо этого используется строка «null».

Метод toString определяется первичным классом Object (§4.3.2). Многие классы переопределяют его, особенно Boolean, Character, Integer, Long, Float, Double и String.

См. §5.4 для деталей контекста преобразования строки.

15.18.1.

Оптимизация конкатенации строк: реализация может выбрать выполнение преобразования и конкатенации за один шаг, чтобы избежать создания и последующего удаления промежуточного объекта String. Чтобы повысить производительность повторяющейся конкатенации строк, компилятор Java может использовать класс StringBuffer или аналогичный метод, чтобы уменьшить количество промежуточных объектов String, которые создаются путем оценки выражения.

Для примитивных типов реализация может также оптимизировать создание объекта-оболочки путем прямого преобразования из примитивного типа в строку.

Оптимизированная версия фактически не выполняет сначала полное преобразование строки в оболочку.

Это хорошая иллюстрация оптимизированной версии, используемой компилятором, хотя и без преобразования примитива, где вы можете увидеть, как компилятор меняет вещи в StringBuilder в фоновом режиме:

http://caprazzi.net/posts/java-bytecode-string-concatenation-and-stringbuilder/


Этот код Java:

public static void main(String[] args) {
    String cip = "cip";
    String ciop = "ciop";
    String plus = cip + ciop;
    String build = new StringBuilder(cip).append(ciop).toString();
}

Создает это - посмотрите, как два стиля конкатенации приводят к одному и тому же байт-коду:

 L0
    LINENUMBER 23 L0
    LDC "cip"
    ASTORE 1
   L1
    LINENUMBER 24 L1
    LDC "ciop"
    ASTORE 2

   // cip + ciop

   L2
    LINENUMBER 25 L2

    NEW java/lang/StringBuilder
    DUP
    ALOAD 1
    INVOKESTATIC java/lang/String.valueOf(Ljava/lang/Object;)Ljava/lang/String;
    INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
    ALOAD 2
    INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;

    ASTORE 3

    // new StringBuilder(cip).append(ciop).toString()

   L3
    LINENUMBER 26 L3

    NEW java/lang/StringBuilder
    DUP
    ALOAD 1
    INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
    ALOAD 2
    INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;

    ASTORE 4
   L4
    LINENUMBER 27 L4
    RETURN

Глядя на приведенный выше пример и на то, как генерируется байтовый код на основе исходного кода в данном примере, вы сможете заметить, что компилятор внутренне преобразовал следующий оператор

cip+ciop; 

в

new StringBuilder(cip).append(ciop).toString();

Другими словами, оператор +конкатенации строк фактически является сокращением более подробной StringBuilderидиомы.

лев
источник
3
Большое спасибо, я не знаком с байтовыми кодами jvm, но сгенерированный код для String plus = cip + ciop; и String build = new StringBuilder (cip) .append (ciop) .toString (); такие же. и у меня вопрос в том, что эта операция нарушает объектную ориентацию?
Pooya
7
Нет, это не так. Перегрузка операторов (как в C ++ и некоторых языках) имеет некоторые недостатки, и разработчики Java посчитали это несколько запутанной концепцией и исключили ее из Java. На мой взгляд, объектно-ориентированный язык должен иметь основные концепции наследования, полиморфизма и инкапсуляции, которые есть в Java.
Lion
2
да, но я думаю, что этот оператор перегружен для класса String
Pooya
3
Да, перегрузка операторов используется в Java для конкатенации типа String, однако вы не можете определить свой собственный оператор (как в C ++, C # и некоторых других языках).
Lion
5
@Pooya: на самом деле "int / int" vs. "int / float" уже является перегрузкой оператора, так что даже C имеет это. Однако у C (и Java) нет перегрузки определяемого пользователем оператора: единственное, что определяет различные способы использования оператора (как в C, так и в Java), - это определение языка (и различие реализовано в компилятор). C ++ отличается тем, что допускает перегрузку определяемого пользователем оператора (что часто называют просто «перегрузкой оператора»).
Joachim Sauer
27

Это функция компилятора Java, которая проверяет операнды +оператора. И на основе операндов генерирует байт-код:

  • Для String он генерирует код для объединения строк
  • Для Numbers он генерирует код для сложения чисел.

Вот что говорится в спецификации Java :

Операторы + и -называются аддитивными операторами. AdditiveExpression: Мультипликативное выражение AdditiveExpression + Мультипликативное выражение AdditiveExpression - Мультипликативное выражение

Аддитивные операторы имеют такой же приоритет и синтаксически ассоциативны слева (они группируются слева направо). Если тип любого из операндов +оператора - Stringэто конкатенация строк.

В противном случае тип каждого из операндов +оператора должен быть типом, который может быть преобразован (§5.1.8) в примитивный числовой тип, в противном случае возникает ошибка времени компиляции.

В любом случае тип каждого из операндов бинарного -оператора должен быть типом, который может быть преобразован (§5.1.8) в примитивный числовой тип, в противном случае возникает ошибка времени компиляции.

Рамеш ПВК
источник
7
Цитата из спецификации вообще не имеет отношения к этому вопросу.
Эрнест Фридман-Хилл
Это отрывок: «Если тип любого операнда оператора + - String, то операция представляет собой конкатенацию строк. В противном случае тип каждого из операндов оператора + должен быть типом, который можно преобразовать (§5.1.8 ) к примитивному числовому типу, иначе возникает ошибка времени компиляции ". Подскажите, пожалуйста, почему это не актуально.
Рамеш ПВК
7
Здесь ничего не говорится о том, как это реализовано, вот в чем вопрос. Думаю, на постере уже понятно, что фича есть.
Эрнест Фридман-Хилл,
14

Как класс String отменяет оператор +?

Это не так. Компилятор это делает. Строго говоря, компилятор перегружает оператор + для строковых операндов.

Маркиз Лорн
источник
6

Прежде всего (+) перегружено, а не отменено

В языке Java предусмотрена специальная поддержка оператора конкатенации строк (+), который был перегружен для объектов Java Strings.

  1. Если левый операнд - String, он работает как конкатенация.

  2. Если левый операнд - целое число, он работает как оператор сложения

Прамод Кумар
источник
3
(2) Если левый операнд является целым числом, он автоматически распаковывается, intи тогда применяются обычные правила Java.
Marquis of Lorne
2
Два правила, приведенные под цитатой, неверны: я считаю, что они должны быть: два примитива (или неупаковываемые классы) = сложение; хотя бы одна строка = конкатенация
mwfearnley 03
4

В языке Java предусмотрена специальная поддержка оператора конкатенации строк (+) и преобразования других объектов в строки. Объединение строк осуществляется через класс StringBuilder(или StringBuffer) и его appendметод.

Джигар Пандья
источник
4

Значение +оператора в применении к нему Stringопределяется языком, как все уже написали. Поскольку вы не находите это достаточно убедительным, примите во внимание следующее:

Ints, float и doubles имеют разные двоичные представления, и поэтому добавление двух int - это другая операция с точки зрения манипуляции с битами, чем добавление двух чисел с плавающей запятой: для int вы можете добавлять бит за битом, переносить бит и проверять переполнение; для поплавков вы должны иметь дело с мантиссами и экспонентами отдельно.

Итак, в принципе, «добавление» зависит от природы «добавляемых» объектов. Java определяет его для строк, а также для целых чисел и чисел с плавающей запятой (длинные, двойные, ...)

Alexis
источник
3

Во время компиляции +оператор обычно заменяется StringBuilderна. Проверьте этот ответ, чтобы узнать больше об этом.

Скорпион
источник
Если это так, то есть ли причина, по которой StringBuilder вообще существует для публичного использования? Есть ли случаи, когда +оператор не заменяется на StringBuilder?
Cemre
2
Вы хотите задать вопрос: «Почему оператор + вообще существует для публичного использования?», Потому что это мерзость здесь. Что касается вашего другого вопроса, я точно не знаю, но думаю, что такого случая нет.
Скорпион
Компилятор может вместо этого использовать concat (), если есть только два элемента. Также компилятор не может заменить concat () на StringBuilder (или использует несколько StringBuilder и добавляет их вместе), когда программист создает строку в длинном вложенном / циклическом коде - использование одного явного StringBuilder будет лучше для производительности.
user158037