Объединение нулевых строк в Java

Почему работает следующее? Я ожидаю, что будет выброшено NullPointerException.

String s = null;
s = s + "hello";
System.out.println(s); // prints "nullhello"

person yavoh    schedule 23.11.2010    source источник
comment
тип s известен во время компиляции, а + operator перегружен для типа String (например, см. Ответ Джонатана). В строке s + "hello" нет вызовов методов и, следовательно, нет шансов для NPE, поскольку нет получателя объекта (и «преобразования кода» должны соблюдать этот контракт). Удачного кодирования.   -  person    schedule 24.11.2010
comment
Я согласен с вашим ходом мыслей явох. Автоматическое преобразование null в строку не является хорошей частью Java. Позор Sun за то, что он делает что-то настолько подверженное ошибкам.   -  person B T    schedule 29.09.2012
comment
@ user166390 Отличное объяснение, не так ли, но печать nullhello по-прежнему остается нелогичным и бесполезным поведением IMO.   -  person aliopi    schedule 08.10.2015
comment
Я думаю, что многие разработчики неправильно понимают, что если я использую нулевой объект, он выйдет из строя. На самом деле, только когда вы вызываете свойство или метод нулевого объекта, это приведет к сбою.   -  person Spark.Bao    schedule 29.07.2016
comment
Если бы мудрые должны были преобразовать ноль в пустую строку, я мог бы немного понять, но преобразовать в буквальный 4-символьный нуль?!   -  person vesperto    schedule 06.02.2019
comment
Таким образом, это по определению оператора + применительно к String значениям. В конце концов, он не пытается вызвать метод для любого из операндов, если только он не имеет другого типа, когда должен быть вызван неявный toString. Очевидно, что целью оператора + является упрощение отображения значения, а не ссылка на длину результата и т. д.   -  person tishma    schedule 18.06.2021


Ответы (5)


Почему это должно работать?

JLS 5, раздел 15.18. .1.1 JLS 8 § 15.18.1 "Оператор конкатенации строк +", ведущий к JLS 8, § 5.1.11 "Преобразование строк" требует, чтобы эта операция выполнялась без ошибок:

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

Как это работает?

Давайте посмотрим на байт-код! Компилятор берет ваш код:

String s = null;
s = s + "hello";
System.out.println(s); // prints "nullhello"

и компилирует его в байт-код, как если бы вместо этого вы написали это:

String s = null;
s = new StringBuilder(String.valueOf(s)).append("hello").toString();
System.out.println(s); // prints "nullhello"

(Вы можете сделать это самостоятельно, используя javap -c< /а>)

Все методы добавления StringBuilder прекрасно обрабатывают null. В этом случае, поскольку null является первым аргументом, вместо него вызывается String.valueOf(), так как StringBuilder не имеет конструктора, принимающего какой-либо произвольный тип ссылки.

Если бы вы вместо этого сделали s = "hello" + s, эквивалентный код был бы таким:

s = new StringBuilder("hello").append(s).toString();

где в этом случае метод append принимает значение null и затем делегирует его String.valueOf().

Примечание. Конкатенация строк на самом деле является одним из редких случаев, когда компилятор решает, какую оптимизацию выполнять. Таким образом, «точный эквивалентный» код может отличаться от компилятора к компилятору. Эта оптимизация разрешена JLS, раздел 15.18. .1.2:

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

Компилятор, который я использовал для определения «эквивалентного кода» выше, был компилятором Eclipse, ecj.

person Mark Peters    schedule 23.11.2010
comment
StringBuilder технически не использует String.valueOf, если вызывается какой-либо метод append, отличный от append(Object) (например, append(String) или append(CharSequence)), хотя null в любом случае заменяется на "null". - person ColinD; 23.11.2010
comment
Спасибо @ColinD, в данном случае я ошибся, так как нет конструктора StringBuilder(Object). Я отредактировал свой пост, чтобы напрямую отразить байт-код. - person Mark Peters; 24.11.2010
comment
Больше объяснений тому, что ведет себя неожиданно неправильно. Он должен напечатать hello. - person aliopi; 08.10.2015
comment
Всегда ли так было в Java? Или это как-то из версий? - person DataDino; 22.09.2016
comment
@aliopi Со всем уважением не согласен. На мой взгляд, он должен выдать NPE, если поведение вообще изменится, но я не думаю, что это действительно поможет. - person Adowrath; 20.07.2017
comment
@Adowrath Я передумал, вы правы, доступ к null должен вызвать NPE. Я думаю, что печать null — это что-то вроде мета-поведения, например, при отладке, когда я хочу увидеть, что находится внутри этой переменной, но по умолчанию немета поведение должно быть метание НПЭ. - person aliopi; 21.07.2017
comment
@aliopi: я думаю, что они приняли оправданное решение, сделав конкатенацию нулевой безопасностью. Это может нарушать принцип наименьшего удивления, но они, вероятно, пытались избежать множества NPE или явных нулевых проверок внутри кода регистрации и диагностики (например, logger.print("doing something to " + obj);). В настоящее время у нас есть лучшая композиция журнала, чем конкатенация строк, но это не обязательно верно в 1995 году. - person Mark Peters; 21.07.2017

См. раздел 5.4 и 15.18 спецификации языка Java:

Преобразование строки применяется только к операндам бинарного оператора +, когда один из аргументов является строкой. В этом единственном особом случае другой аргумент + преобразуется в строку, а новая строка, представляющая собой конкатенацию двух строк, является результатом +. Преобразование строк подробно описано в описании оператора конкатенации строк +.

и

Если только одно выражение операнда имеет тип String, то преобразование строки выполняется для другого операнда, чтобы создать строку во время выполнения. Результатом является ссылка на объект String (вновь созданный, если только выражение не является выражением-константой времени компиляции (§15.28)), представляющим собой конкатенацию двух строк операндов. Символы левого операнда предшествуют символам правого операнда во вновь созданной строке. Если операнд типа String имеет значение null, то вместо этого операнда используется строка null.

person Jonathon Faust    schedule 23.11.2010
comment
Да! Наконец, ответ с некоторыми хорошими кавычками без перехода на «StringBuilder» (это оптимизация, которую компилятор может выполнить или не выполнить). - person ; 24.11.2010

Вторая строка преобразуется в следующий код:

s = (new StringBuilder()).append((String)null).append("hello").toString();

Методы добавления могут обрабатывать null аргументов.

person Roland Illig    schedule 23.11.2010

Вы не используете "null" и поэтому не получаете исключения. Если вам нужен NullPointer, просто сделайте

String s = null;
s = s.toString() + "hello";

И я думаю, что вы хотите сделать это:

String s = "";
s = s + "hello";
person krico    schedule 23.11.2010
comment
Вы используете null, вы просто не пытаетесь его разыменовать. - person GregT; 16.04.2018

Это поведение указано в String.valueOf(Object) метод. Когда вы выполняете конкатенацию, valueOf используется для получения представления String. Существует особый случай, когда Объект равен null, и в этом случае используется строка "null".

public static String valueOf(Object obj)

Возвращает строковое представление аргумента Object.

Параметры: obj - Объект.

Возвращает:

если аргумент нулевой, то строка равна "null"; в противном случае возвращается значение obj.toString().

person wkl    schedule 23.11.2010