在Java中连接空string

为什么以下工作? 我会期待一个NullPointerException抛出。

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

为什么它必须工作?

JLS 5的第15.18.1.1节 JLS 8第15.18.1节“string连接操作符+” ,导致JLS 8,第5.1.11节“string转换” ,要求此操作成功而不失败:

…现在只需要考虑参考值。 如果引用为空,则将其转换为string“null” (四个ASCII字符n,u,l,l)。 否则,转换就像通过调用不带参数的被引用对象的toString方法一样执行。 但是如果调用toString方法的结果为空,则使用string“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的append方法都处理null就好了。 在这种情况下,因为null是第一个参数,所以String.valueOf()被调用,因为StringBuilder没有一个任意的引用types的构造函数。

如果你已经完成了s = "hello" + s ,那么等价的代码将是:

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

在这种情况下,append方法接受null, 然后将其委托给String.valueOf()

注意:string连接实际上是编译器决定要执行哪些优化的罕见位置之一。 因此,“准确的等价”代码可能因编译器而异。 JLS第15.18.1.2节允许进行这种优化:

为了提高重复string连接的性能,Java编译器可以使用StringBuffer类或类似技术来减less通过评估expression式创build的中间String对象的数量。

我用来确定上面的“等效代码”的编译器是Eclipse的编译器ecj

请参阅Java语言规范的第5.4节和第15.18节:

其中一个参数是string时,string转换仅适用于二元运算符的操作数。 在这个特殊情况下,+的另一个参数被转换为一个string,并且一个新的string是两个string的串联结果。 string转换在string连接+运算符的描述中详细说明。

如果只有一个操作数expression式是Stringtypes,则在另一个操作数上执行string转换以在运行时产生一个string。 结果是对String对象的引用(新创build的,除非expression式是编译时常量expression式(第15.28节)),它是两个操作数string的串联。 左侧操作数的字符位于新创build的string中右侧操作数的字符之前。 如果Stringtypes的操作数为空,则使用string“null”而不是该操作数。

第二行转换为以下代码:

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

append方法可以处理null参数。

你没有使用“空”,因此你没有得到例外。 如果你想要NullPointer,就这样做

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

我想你想要做的是:

 String s = ""; s = s + "hello"; 

这是在Java API的String.valueOf(Object)方法中指定的行为。 当你进行连接时, valueOf被用来获取String表示。 如果Object为null ,则会出现特殊情况,在这种情况下,将使用string"null"

public static String valueOf(Object obj)

返回Object参数的string表示forms。

参数:obj – 一个对象。

返回:

如果参数为null,那么一个string等于“null”; 否则,返回obj.toString()的值。