Java 字节码编译中如何处理字符串串联?

How is string concatenation handled in Java bytecode compilation?

提问人:Irfan Latif 提问时间:8/31/2023 最后编辑:Irfan Latif 更新时间:9/1/2023 访问量:114

问:

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() 回调。

而且我不确定是否还有其他可能的行为。

字符串串联 java 字节码 asm 字节码操作

评论

1赞 Stephen C 8/31/2023
您似乎在说同一个类在“不同的地方”被编译为两个不同的字节码序列。这没有多大意义。通常,一个 Java(源代码)类只编译一次。您实际上是说您有两个来自不同版本的 .class 文件吗?如果是,可能的解释是它们是使用来自不同 JDK 的不同编译器编译的。第一个版本看起来像一个较旧的编译器。javac
2赞 Stephen C 8/31/2023
请参见 baeldung.com/java-string-concatenation-invoke-dynamic。用于字符串连接是在 Java 9 中引入的。invokedynamic
2赞 Kayaman 8/31/2023
这是在 Java 9 中完成的“Indify String concatenation”的 JEP
1赞 Holger 8/31/2023
最后一部分表明您有一个实际任务需要解决,但您没有描述。不,第一个变体不包含单个指令,从 up 到 的整个序列是字符串连接。同样,第二个变体也包含多个指令,因为前面的指令也属于字符串串联。您是否必须考虑这些变体以外的其他因素取决于您的实际任务。ldcnew #1 // java/lang/StringBuilderinvokevirtual #13 // java/lang/StringBuilder.toString:()Ljava/lang/String;aload_1invokedynamic
1赞 Holger 8/31/2023
在实践中,您至少会遇到四种策略。但是,您是否需要在语义上识别字符串连接的代码,那么您还必须考虑,例如 或者,或者从字面上看,那么你就遇到了一个问题,即你无法将第一个变体与源代码片段生成的代码区分开来。未来的 Java 版本呢,您是否需要检测?"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}"

答:

0赞 Irfan Latif 9/1/2023 #1

字符串串联行为在 Java 9 中已更改。

在前一种情况下,该类属于使用 .而在后一种情况下,该类属于一个库,该库是用 .因此,编译的字节码与字符串连接不同。-source 8 -target 8-source 11 -target 11

感谢 @Stephen C@Kayaman 提供的链接:


为了完整起见,正如@Holger注释中提到的,在编译字节码中的字符串操作方面是相同的。因为在这三种情况下,字符串常量首先被推送到堆栈上(在那里可以简单地操作)。但是在 的情况下,首先使用 将局部字符串变量的引用推送到堆栈上。StringBuilderString.concat()String.join()ldcStringConcatFactory.makeConcatWithConstants()aload

评论

1赞 Holger 9/1/2023
如果存在这样的常量部分,则该指令仅将字符串串联的常量部分推送到操作数堆栈。对于 ,指令将在局部变量被推送之后。但是对于 ,没有常数部分,因此没有指令。此外,即使没有任何字符串连接,字节码也可以包含指令。如前所述,您应该解释您的实际任务,然后,我们可以提出解决方案。ldcpublic static String test(String s1, String s2) { return s1 + s2 + "blah"; }ldcpublic static String test(String s1, String s2) { return s1 + s2; }ldcldc
1赞 Holger 9/1/2023
参见 什么是 XY 问题?
0赞 Irfan Latif 9/1/2023
@Holger我在问题中问得很清楚,已经回答得很清楚了。它解决了我的问题。我已经修复了我的代码。我在问题中简要介绍了字节码操作的背景,以避免“你为什么关心?”类型的注释。如果我在一般情况下需要有关字节码操作的帮助,我会发布另一个问题。感谢您的大力帮助。ldc
1赞 Holger 9/1/2023
不,所提出的问题尚未得到回答。正如我之前所说,在实践中至少可以遇到四种不同的策略。你只涵盖了其中的两个。你错过的其他人是否重要,取决于你的实际任务。我不会浪费时间来解释你到目前为止错过了什么。也许这对你来说并不重要。但也许你只是在制造一个定时炸弹。谁知道呢。祝你好运。