final关键字如何处理字符串不变性?

lb3vh1jj  于 2021-06-30  发布在  Java
关注(0)|答案(3)|浏览(347)

这个问题在这里已经有了答案

如何在java中连接最终字符串(1个答案)
26天前关门了。
我有以下代码。我理解java字符串不变性和字符串常量池的概念。我不明白为什么在下面的程序中'name1==name2'结果为false,'name2==name3'结果为true。字符串变量name1、name2和name3是如何放置在字符串常量池中的?

public class Test {
    public static void main(String[] args) {
        final String firstName = "John";
        String lastName = "Smith";
        String name1 = firstName + lastName;
        String name2 = firstName + "Smith";
        String name3 = "John" + "Smith";
        System.out.println(name1 == name2);
        System.out.println(name2 == name3);
   }
}

Output:
false
true
rwqw0loc

rwqw0loc1#

javap -c Test 在你编译代码之后,
你会看到的

Compiled from "Test.java"                                                                                                     
public class Test {                                                                                                           
  public Test();                                                                                                              
    Code:                                                                                                                     
       0: aload_0                                                                                                             
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V                                           
       4: return                                                                                                              

  public static void main(java.lang.String[]);                                                                                
    Code:                                                                                                                     
       0: ldc           #2                  // String Smith                                                                   
       2: astore_2                                                                                                            
       3: aload_2                                                                                                             
       4: invokedynamic #3,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
       9: astore_3                                                                                                            
      10: ldc           #4                  // String JohnSmith                                                               
      12: astore        4                                                                                                     
      14: ldc           #4                  // String JohnSmith                                                               
      16: astore        5                                                                                                     
      18: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;                               
      21: aload_3                                                                                                             
      22: aload         4                                                                                                     
      24: if_acmpne     31                                                                                                    
      27: iconst_1                                                                                                            
      28: goto          32                                                                                                    
      31: iconst_0                                                                                                            
      32: invokevirtual #6                  // Method java/io/PrintStream.println:(Z)V                                        
      35: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;                               
      38: aload         4                                                                                                     
      40: aload         5                                                                                                     
      42: if_acmpne     49                                                                                                    
      45: iconst_1                                                                                                            
      46: goto          50                                                                                                    
      49: iconst_0                                                                                                            
      50: invokevirtual #6                  // Method java/io/PrintStream.println:(Z)V                                        
      53: return                                                                                                              
}

如你所见

4: invokedynamic #3,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;

public static void main(java.lang.String[]); ,这是运行时实际编译的字节码 firstname + lastname . 因此生成的字符串将不会与常量池中的“johnsmith”具有相同的哈希代码。
在哪里

10: ldc           #4                  // String JohnSmith

14: ldc           #4                  // String JohnSmith

这是编译器在执行这两种操作时生成的字节码 firstname + "Smith" 以及 "John" + "Smith" 这意味着两者实际上都是从常量池中读取的。
这就是为什么在比较name1和name2时 == 它会回来的 false . 自 name2 以及 name3 从常量池中引用相同的字符串。亨克它回来了 true 与…相比 == . 这就是为什么比较2字符串不是一个好主意的原因 == . 请使用 String.equals() 进行字符串比较时。
因为两者

bq3bfh9z

bq3bfh9z2#

让我们看看字节码 final :

public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #7                  // String Smith
       2: astore_1
       3: aload_1
       4: invokedynamic #9,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
       9: astore_2
      10: ldc           #13                 // String JohnSmith
      12: astore_3
      13: ldc           #13                 // String JohnSmith
      15: astore        4
      17: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
      20: aload_2
      21: aload_3
      22: if_acmpne     29
      25: iconst_1
      26: goto          30
      29: iconst_0
      30: invokevirtual #21                 // Method java/io/PrintStream.println:(Z)V
      33: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
      36: aload_3
      37: aload         4
      39: if_acmpne     46
      42: iconst_1
      43: goto          47
      46: iconst_0
      47: invokevirtual #21                 // Method java/io/PrintStream.println:(Z)V
      50: return
}

字节码没有 final :

public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #7                  // String John
       2: astore_1
       3: ldc           #9                  // String Smith
       5: astore_2
       6: aload_1
       7: aload_2
       8: invokedynamic #11,  0             // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
      13: astore_3
      14: aload_1
      15: invokedynamic #15,  0             // InvokeDynamic #1:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
      20: astore        4
      22: ldc           #18                 // String JohnSmith
      24: astore        5
      26: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
      29: aload_3
      30: aload         4
      32: if_acmpne     39
      35: iconst_1
      36: goto          40
      39: iconst_0
      40: invokevirtual #26                 // Method java/io/PrintStream.println:(Z)V
      43: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
      46: aload         4
      48: aload         5
      50: if_acmpne     57
      53: iconst_1
      54: goto          58
      57: iconst_0
      58: invokevirtual #26                 // Method java/io/PrintStream.println:(Z)V
      61: return
}

正如你所看到的 final ,java可以识别 + 所以它用常量字符串替换串联 "JohnSmith" 在编译时。唯一的要求 makeConcatWithConstants (连接字符串)是为 firstName + lastName ,自 lastName 不是最终决定。
在第二个示例中,有两个调用 makeConcatWithConstants ,一个 firstName + lastName 再来一杯 firstName + "Smith" ,因为java无法识别 firstName 作为常量。
这就是为什么 name1 == name2false 在您的示例中: name2 是一个常数 "JohnSmith" 在字符串池中 name1 在运行时动态计算。然而, name2 以及 name3 这两个常量都在字符串池中,这就是为什么 name2 == name3true .

nkkqxpd9

nkkqxpd93#

答案相当简单:作为一种捷径,java将某些概念视为所谓的“编译时常量”(compiletime constant,ctc)。其思想是在编译级别完全内联一个变量(这很特别;正常情况下 javac 基本上,只需使用非常简单且易于理解的转换将java源文件猛击为类文件,fancypants优化在运行时进行(hotspot)。
例如,如果您这样做:
保存到 UserOfBatch.java :

class BatchOConstants {
    public static final int HELLO = 5;
}

public class UserOfBatch {
    public static void main(String[] args) {
        System.out.println(BatchOConstants.HELLO);
    }
}

在命令行上运行:

> javac UserOfBatch.java
> java UserOfBatch
5
> javap -c UserOfBatch # javap prints bytecode
 public static void main(java.lang.String[]);
    Code:
       0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: iconst_5
       4: invokevirtual #15                 // Method java/io/PrintStream.println:(I)V
       7: return

看看上面的3号线。 iconst_5 . 那5个?它是硬编码的!!
未提及 BatchOConstants 剩下的。让我们测试一下:
在命令行上:

> rm BatchOConstants.class
> java UserOfBatch
5

真 的。代码运行时,即使它丢失了应该提供5的类文件,因此证明它是由编译器本身而不是运行时“硬编码”的。
“观察”任何值的真实性的另一种方法是注解图。注解参数必须由javac硬编码到类文件中,因此不能传递表达式。鉴于:

public @interface Foo {
    long value();

我不会写: @Foo(System.currentTimeMillis()) ,因为 System.cTM 显然不是编译时常量。但我会写作 @Foo(SomeClass.SOME_STATIC_FINAL_LONG_FIELD) 假设分配给s\u s\u f\u l\u f的值是编译时常量。如果不是,那 @Foo(...) 代码无法编译。如果是,它将编译:现在决定代码是否编译。
关于何时允许编译器将某个内容解析为“编译时常量”并进行内联狂欢,有一些特定的规则。例如, null 永远不是内联常量。过于简单化,但:
对于字段,字段必须是静态的和final的,并且具有一个原语或字符串类型,该原语或字符串类型使用一个不为null的常量表达式当场初始化(同时,不要稍后在静态块中初始化)。
对于局部变量,规则是非常相似的,不同的是,它们必须是静态的,因为它们不能是静态的。除此之外,所有的fixin都适用:final、primitive或string、non-null、当场初始化并使用常量表达式。
不幸的是,一个地方的真实性很难直接观察到。不过,您编写的代码是间接观察它的好方法。你的指纹证明了 firstName ctc和 lastName 不是。
不过,我们可以观察到一些精选的东西。所以让我们把你的代码,编译,然后扔到javap上,看看结果。通过jonas konrad的在线javap工具,让我们分析一下:

public Main() {
        final String a = "hello";
        String b = "world";
        String c = a + "!";
        String d = b + "!";
        System.out.println(c == "hello!");
        System.out.println(d == "world!");
    }

相关部分:

0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: ldc           #7                  // String hello
         6: astore_1
        start local 1 // java.lang.String a
         7: ldc           #9                  // String world
         9: astore_2
        start local 2 // java.lang.String b
        10: ldc           #11                 // String hello!
        12: astore_3
        start local 3 // java.lang.String c
        13: aload_2
        14: invokedynamic #13,  0             // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
        19: astore        4
        start local 4 // java.lang.String d

注意“启动本地2”(即c;javap从0开始计数)显示正在加载 hello! 作为一个完整的常量,但“start local 3”(即d)显示加载2个常量并调用makeconcat将它们连接在一起。

相关问题