字符串连接运算符()

+符号可以表示 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 编译器可以进一步优化,如果它可以推断出 s1s2 不能是这样的话。)但是请注意,这种优化只允许在一个表达式中使用。

简而言之,如果你担心字符串连接的效率:

  • 如果你在循环(或类似)中重复连接,请进行手动优化。
  • 不要手动优化单个连接表达式。