在您的两种情况下,选项 1 每次都会更快,除非 JITC 做了我没想到的事情。我真的不希望它会有所作为,除非你非常频繁地调用这些方法。
为什么?因为您实际上不使用选项 1 创建新对象。编译器应该执行常量折叠,将"Hello" + "World..!" 转换为"HelloWorld..!"。而且因为这是一个编译时常量String,它会在VM 启动时自动嵌入到String 池中。因此,每次调用该方法时,您都会得到对该“规范”String 的引用。不执行对象创建。
在选项 2 中,您始终创建多个对象——StringBuffer(顺便说一下,您应该使用StringBuilder)、支持char[],以及结果String(至少) .而且在一个紧密的循环中这样做并不是很有效。
此外,选项 1 更具可读性,这始终是您在编写代码时应该考虑的问题。
证明:
鉴于此测试代码:
public class Test {
public String getTestMessageA() {
return "Hello" + "World..!";
}
public String getTestMessageB() {
return new StringBuffer("Hello").append("World..!").toString();
}
}
使用javac -XD-printflat 编译向我们展示了此代码在编译为字节码之前被处理的内容:
public class Test {
public Test() {
super();
}
public String getTestMessageA() {
return "HelloWorld..!";
}
public String getTestMessageB() {
return new StringBuffer("Hello").append("World..!").toString();
}
}
注意"Hello" + "World..!" 如何在编译时 转换为单个 String。所以String 连接不是第一个选项中发生的事情。
现在让我们看看字节码。这是常量池:
Constant pool:
#1 = Methodref #10.#20 // java/lang/Object."<init>":()V
#2 = String #21 // HelloWorld..!
#3 = Class #22 // java/lang/StringBuffer
#4 = String #23 // Hello
#5 = Methodref #3.#24 // java/lang/StringBuffer."<init>":(Ljava/lang/String;)V
#6 = String #25 // World..!
#7 = Methodref #3.#26 // java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
#8 = Methodref #3.#27 // java/lang/StringBuffer.toString:()Ljava/lang/String;
这是选项 1 的字节码:
public java.lang.String getTestMessageA();
Code:
0: ldc #2 // String HelloWorld..!
2: areturn
嗯,这很短。如您所见,JVM 从池中加载一个常量 (ldc) 并返回它。没有直接在方法中创建对象。
下面是选项 2 的字节码:
public java.lang.String getTestMessageB();
Code:
0: new #3 // class java/lang/StringBuffer
3: dup
4: ldc #4 // String Hello
6: invokespecial #5 // Method java/lang/StringBuffer."<init>":(Ljava/lang/String;)V
9: ldc #6 // String World..!
11: invokevirtual #7 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer;
14: invokevirtual #8 // Method java/lang/StringBuffer.toString:()Ljava/lang/String;
17: areturn
因此,这段代码创建了一个新的StringBuffer,从字符串池中加载适当的常量,为每个常量调用append() 方法,然后在缓冲区上调用toString() 方法。 toString() 是这样实现的:
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
所以选项 2 每次调用时都会创建两个新对象,并且还会执行更多指令。因此,选项 1 会更快。