提问人:Irfan Latif 提问时间:8/31/2023 最后编辑:Irfan Latif 更新时间:9/1/2023 访问量:114
Java 字节码编译中如何处理字符串串联?
How is string concatenation handled in Java bytecode compilation?
问:
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:
在前一种情况下,调用指令,可以通过重写 MethodVisitor.visitLdcInsn()
方法拦截该指令,并且可以操作 String。ldc
为了解决后一种行为,还需要覆盖 MethodVisitor.visitInvokeDynamicInsn()
回调。
而且我不确定是否还有其他可能的行为。
答:
0赞
Irfan Latif
9/1/2023
#1
字符串串联行为在 Java 9 中已更改。
在前一种情况下,该类属于使用 .而在后一种情况下,该类属于一个库,该库是用 .因此,编译的字节码与字符串连接不同。-source 8 -target 8
-source 11 -target 11
感谢 @Stephen C 和 @Kayaman 提供的链接:
为了完整起见,正如@Holger注释中提到的,在编译字节码中的字符串操作方面是相同的。因为在这三种情况下,字符串常量首先被推送到堆栈上(在那里可以简单地操作)。但是在 的情况下,首先使用 将局部字符串变量的引用推送到堆栈上。StringBuilder
String.concat()
String.join()
ldc
StringConcatFactory.makeConcatWithConstants()
aload
评论
1赞
Holger
9/1/2023
如果存在这样的常量部分,则该指令仅将字符串串联的常量部分推送到操作数堆栈。对于 ,指令将在局部变量被推送之后。但是对于 ,没有常数部分,因此没有指令。此外,即使没有任何字符串连接,字节码也可以包含指令。如前所述,您应该解释您的实际任务,然后,我们可以提出解决方案。ldc
public static String test(String s1, String s2) { return s1 + s2 + "blah"; }
ldc
public static String test(String s1, String s2) { return s1 + s2; }
ldc
ldc
1赞
Holger
9/1/2023
参见 什么是 XY 问题?
0赞
Irfan Latif
9/1/2023
@Holger我在问题中问得很清楚,已经回答得很清楚了。它解决了我的问题。我已经修复了我的代码。我在问题中简要介绍了字节码操作的背景,以避免“你为什么关心?”类型的注释。如果我在一般情况下需要有关字节码操作的帮助,我会发布另一个问题。感谢您的大力帮助。ldc
1赞
Holger
9/1/2023
不,所提出的问题尚未得到回答。正如我之前所说,在实践中至少可以遇到四种不同的策略。你只涵盖了其中的两个。你错过的其他人是否重要,取决于你的实际任务。我不会浪费时间来解释你到目前为止错过了什么。也许这对你来说并不重要。但也许你只是在制造一个定时炸弹。谁知道呢。祝你好运。
评论
javac
invokedynamic
ldc
new #1 // java/lang/StringBuilder
invokevirtual #13 // java/lang/StringBuilder.toString:()Ljava/lang/String;
aload_1
invokedynamic
"This is the message: " .concat(msg)
String.join("", "This is the message: ", msg)
new StringBuilder().append("This is the message: ").append(msg) .toString()
STR ."This is the message: \{msg}"