public class TestException extends Exception {
public TestException(String msg) {
super("This is the message: " + msg);
}
}
以上代码编译后为:
public class TestException extends java.lang.Exception {
public TestException(java.lang.String);
Code:
0: aload_0
1: new #1 // class java/lang/StringBuilder
4: dup
5: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
8: ldc #7 // String This is the message:
10: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
13: aload_1
14: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17: invokevirtual #13 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
20: invokespecial #17 // Method java/lang/Exception."<init>":(Ljava/lang/String;)V
23: return
}
这里创建了一个
StringBuilder
对象来连接字符串。
在另一个地方的同一个应用程序中,完全相同的类被编译为:
public class TestException extends java.lang.Exception {
public TestException(java.lang.String);
Code:
0: aload_0
1: aload_1
2: invokedynamic #1, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
7: invokespecial #5 // Method java/lang/Exception."<init>":(Ljava/lang/String;)V
10: return
}
这里调用
StringConcatFactory.makeConcatWithConstants()
来连接字符串。
哪些因素决定了这种不同的行为?如何预测/控制?
PS:
在前一种情况下,会调用
ldc
指令,可以通过重写 MethodVisitor.visitLdcInsn()
方法来拦截该指令,并且可以对 String 进行操作。
MethodVisitor.visitInvokeDynamicInsn()
回调也需要被重写。
我不确定是否还有其他可能的行为。
Java 9 中的字符串连接行为已更改。
在前一种情况下,该类属于使用
-source 8 -target 8
构建的主应用程序。而在后一种情况下,该类属于一个库,该库(应用程序的依赖项)是用 -source 11 -target 11
构建的。因此编译后的字节码有所不同。字符串连接。
感谢 @Stephen C 和 @Kayaman 提供链接:
为了完整起见,正如评论中提到的@Holger,在编译的字节码中进行字符串操作时,
StringBuilder
、String.concat()
和String.join()
是相同的。因为在所有三种情况下,字符串常量首先使用 ldc
压入堆栈(可以简单地对其进行操作)。但在 StringConcatFactory.makeConcatWithConstants()
的情况下,首先使用 aload
将局部字符串变量的引用推入堆栈。