在 Java 中连接空字符串

为什么下面的代码可以工作? 我希望抛出一个 NullPointerException

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

为什么 必须的能用?

通向 JLS 8,5.1.11“字符串转换”< 罢工 > JLS 5,15.18.1.1 JLS 815.18.1“ String Concatenation Operator +”要求这种操作必须成功而不能失败:

现在只需要考虑参考值。如果引用为 null,则将其转换为字符串“ 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的 append 方法都能很好地处理 null。在本例中,由于 null是第一个参数,因此调用了 String.valueOf(),因为 StringBuilder 没有任何任意引用类型的构造函数。

如果用 s = "hello" + s代替,等效的代码将是:

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

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

注意: String 连接实际上是编译器决定执行哪些优化的少数几个地方之一。因此,“精确等效”代码可能因编译器而异。JLS 第15.18.1.2节允许这种优化:

为了提高重复字符串串联的性能,Java 编译器可以使用 StringBuffer 类或类似的技术来减少通过计算表达式而创建的中间字符串对象的数量。

我用来确定上述“等价代码”的编译器是 Eclipse 的编译器 Ej

第二行转换为以下代码:

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

Append 方法可以处理 null参数。

这是 JavaAPI 的 String.valueOf(Object)方法中指定的行为。当进行连接时,使用 valueOf来获得 String表示。如果 Object 是 null,则有一种特殊情况,在这种情况下使用字符串 "null"

public static String valueOf(Object obj)

返回 Object 参数的字符串表示形式。

参数: 对象。

返回:

如果参数为 null,则返回一个等于“ null”的字符串; 否则,返回 objec.toString ()的值。

您没有使用“ null”,因此不会得到异常。如果你想要 NullPointer,就照做

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

我觉得你应该这么做:

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

请参阅 Java 语言规范的 5.415.18部分:

字符串转换仅应用于 二进制 + 运算符的操作数 其中一个参数是 String 一个特例,另一个 参数转换为 字符串,以及一个新的字符串,它是 两个字符串的串联是 字符串转换的结果 中详细指定 字符串的说明 连接 + 运算符连接 + 运算符。

还有

如果只有一个操作数表达式为 类型 String,则字符串转换为 在另一个操作数上执行到 在运行时生成一个字符串 Result 是对 String 的引用 对象(新创建的,除非 表达式是编译时常量 表达式(15.28) 两个操作数的串联 字符串的字符 属性之前的左操作数 右操作数的字符 在新创建的字符串中。如果 String 类型的操作数为 null,则 使用字符串“ null”代替 那个操作数。