Java中如何对最终字符串进行拼接?

18

当我编译这段代码时。

public class InternTest {
    public static void main(String...strings ){
        final String str1="str";
        final String str2="ing";
        String str= str1+str2;

    }
}

它会生成以下字节码:

public static void main(java.lang.String...);
   flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
   Code:
     stack=1, locals=4, args_size=1
        0: ldc           #16                 // String str
        2: astore_1
        3: ldc           #18                 // String ing
        5: astore_2
        6: ldc           #20                 // String string
        8: astore_3
        9: return

因此,字符串字面量"string"已经存在于常量池中,该常量池被推送到堆栈上的6: ldc #20 // String string行。

JSL引用:

  

来自JLS §4.12.4 - final变量:

     

原始类型或String类型的变量,是final并使用编译时常量表达式(§15.28)进行初始化,则称为常量变量。

     

还来自JLS §15.28 -ConstantExpression:

     

类型为String的编译时常量表达式总是被“interned”以共享唯一实例,使用方法String#intern()。

所以我知道str1和str2只要创建就会被放入进常量池中。 "str"和"ing"将在String str = str1 + str2;行共享内存。

但是,为什么str1 + str2直接在常量字符串池中生成"string"?而不需要像没有写final那样调用任何String Builder类? 我不确定是否与intern有关,于是我写了这个代码片段。
public class IntermTest {
    public static void main(String...strings ){
         String str1=("str").intern();
        String str2=("ing").intern();
        String str= str1+str2;

    }
}

但是当我生成字节码时,我得到了这个结果

public static void main(java.lang.String...);
    flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
    Code:
      stack=3, locals=4, args_size=1
         0: ldc           #16                 // String str
         2: invokevirtual #18                 // Method java/lang/String.intern:
()Ljava/lang/String;
         5: astore_1
         6: ldc           #24                 // String ing
         8: invokevirtual #18                 // Method java/lang/String.intern:
()Ljava/lang/String;
        11: astore_2
        12: new           #26                 // class java/lang/StringBuilder
        15: dup
        16: aload_1
        17: invokestatic  #28                 // Method java/lang/String.valueOf
:(Ljava/lang/Object;)Ljava/lang/String;
        20: invokespecial #32                 // Method java/lang/StringBuilder.
"<init>":(Ljava/lang/String;)V
        23: aload_2
        24: invokevirtual #35                 // Method java/lang/StringBuilder.
append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        27: invokevirtual #39                 // Method java/lang/StringBuilder.
toString:()Ljava/lang/String;
        30: astore_3
        31: return

事实上,它还使用stringBuilder进行字符串连接。因此,它与final有些关系。关于final字符串有什么特殊的地方我不知道吗?


有些东西告诉我,这将是你下一个问题的答案:https://dev59.com/o2Ik5IYBdhLWcg3wRcUo#19418517 - Pshemo
@Pshemo 当这一点变得清楚时...我不认为会引起任何问题...无论如何,感谢您提出这样好的问题.. :) 每个问题都是很好的学习曲线! - Ankur Anand
1个回答

19

http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.28指出:

引用常量变量(§4.12.4)的简单名称(§6.5.6.1)是常量表达式。

http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.28还说:

常量表达式是指表示基本类型值或不会突然终止并且只使用以下内容构成的String类型值的表达式:

  • 基本类型字面量和String类型字面量(§3.10.1、§3.10.2、§3.10.3、§3.10.4、§3.10.5)
  • [...]
  • 加法和减法运算符+和-(§15.18)
  • [...]
  • 引用常量变量(§4.12.4)的简单名称(§6.5.6.1)。

示例15.28-1.常量表达式

[...]

“The integer " + Long.MAX_VALUE + " is mighty big.”

因为这两个变量是常量表达式,编译器进行了拼接:

String str = str1 + str2;

以相同的方式编译

String str = "str" + "ing";

编译方式与其相同

String str = "string";

如果编译器可以这样做,为什么它会在字节码中隐藏或者说它是在其他地方完成的呢?因为如果我没错的话,现代Java编译器会将我的+操作转换为StringBuilderappend方法。 - Ankur Anand
6
就像我之前说的那样,字符串连接操作是由编译器完成的,而不是在运行时完成的。对于代码行String str = str1 + str2生成的字节码和String str = "string"生成的字节码完全相同,因为str1 + str2是一个常量表达式。 - JB Nizet
3
因为str1+str2是一个常量表达式。更确切地说,因为str1str2不仅仅是任何常量,而是编译时常量,这使得编译器可以计算一次并将结果放置在代码中,而不是在每次执行此代码时重新计算它。 - Pshemo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接