字符串连接运算符()
+
符号可以表示 Java 中的三个不同运算符:
- 如果
+
之前没有操作数,则它是一元的 Plus 运算符。 - 如果有两个操作数,它们都是数字。那么它是二元加法运算符。
- 如果有两个操作数,并且其中至少有一个是
String
,那么它就是二元连接运算符。
在简单的情况下,Concatenation 运算符连接两个字符串以提供第三个字符串。例如:
String s1 = "a String";
String s2 = "This is " + s1; // s2 contains "This is a String"
当两个操作数中的一个不是字符串时,它将转换为 String
,如下所示:
-
一个操作数,其类型是基本类型被转换为如果通过调用
toString()
在装箱值。 -
通过调用操作数的
toString()
方法转换其类型为引用类型的操作数。如果操作数是null
,或者如果toString()
方法返回null
,则使用字符串文字null
。
例如:
int one = 1;
String s3 = "One is " + one; // s3 contains "One is 1"
String s4 = null + " is null"; // s4 contains "null is null"
String s5 = "{1} is " + new int[]{1}; // s5 contains something like
// "{} is [I@xxxxxxxx"
s5
示例的解释是数组类型的 toString()
方法继承自 java.lang.Object
,行为是生成一个由类型名称和对象的标识哈希码组成的字符串。
指定 Concatenation 运算符以创建新的 String
对象,除非表达式是常量表达式。在后一种情况下,表达式在编译类型时计算,其运行时值等同于字符串文字。这意味着在拆分长字符串文字时没有运行时开销,如下所示:
String typing = "The quick brown fox " +
"jumped over the " +
"lazy dog"; // constant expression
优化和效率
如上所述,除常量表达式外,每个字符串连接表达式都会创建一个新的 String
对象。考虑以下代码:
public String stars(int count) {
String res = "";
for (int i = 0; i < count; i++) {
res = res + "*";
}
return res;
}
在上面的方法中,循环的每次迭代将创建一个比前一次迭代长一个字符的新 String
。每个连接都复制操作数字符串中的所有字符以形成新的 String
。因此,stars(N)
将:
- 创造
N
新的String
对象,扔掉除了最后一个之外的所有对象, - 复制
N * (N + 1) / 2
个字符,和 - 生成
O(N^2)
字节的垃圾。
这对于大型的 tihuan 来说非常昂贵 23。实际上,任何在循环中连接字符串的代码都容易出现这个问题。写这个的更好方法如下:
public String stars(int count) {
// Create a string builder with capacity 'count'
StringBuilder sb = new StringBuilder(count);
for (int i = 0; i < count; i++) {
sb.append("*");
}
return sb.toString();
}
理想情况下,你应该设置 StringBuilder
的容量,但如果这不可行,则类将自动增大构建器用于保存字符的后备数组。 (注意:实现会以指数方式扩展后备数组。这种策略可以将字符数量复制到 O(N)
而不是 O(N^2)
。)
有些人将此模式应用于所有字符串连接。但是,这是不必要的,因为 JLS 允许 Java 编译器优化单个表达式中的字符串连接。例如:
String s1 = ...;
String s2 = ...;
String test = "Hello " + s1 + ". Welcome to " + s2 + "\n";
将典型的字节码编译器是这样进行优化;
StringBuilder tmp = new StringBuilder();
tmp.append("Hello ")
tmp.append(s1 == null ? "null" + s1);
tmp.append("Welcome to ");
tmp.append(s2 == null ? "null" + s2);
tmp.append("\n");
String test = tmp.toString();
(JIT 编译器可以进一步优化,如果它可以推断出 s1
或 s2
不能是这样的话。)但是请注意,这种优化只允许在一个表达式中使用。
简而言之,如果你担心字符串连接的效率:
- 如果你在循环(或类似)中重复连接,请进行手动优化。
- 不要手动优化单个连接表达式。