首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >final关键字如何处理字符串的不变性?

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

Stack Overflow用户
提问于 2020-12-13 22:10:36
回答 3查看 52关注 0票数 0

我有以下代码。我理解java字符串不变性和字符串常量池的概念。我不明白为什么在下面的程序中'name1 == name2‘结果为假,而'name2 == name3’结果为真。字符串变量name1、name2和name3如何放在字符串常量池中?

代码语言:javascript
运行
复制
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
EN

回答 3

Stack Overflow用户

发布于 2020-12-13 23:31:49

答案相当简单:作为一种捷径,java将某些概念视为所谓的“编译时间常数”(CTC)。这个想法是在编译级完全内联一个变量(这是非常特别的;通常javac基本上只是使用非常简单和容易理解的转换将你的java源文件转化成一个类文件,并且fancypants优化在运行时在hotspot期间发生)。

例如,如果您这样做:

保存到UserOfBatch.java

代码语言:javascript
运行
复制
class BatchOConstants {
    public static final int HELLO = 5;
}

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

在命令行上运行:

代码语言:javascript
运行
复制
> 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

看看上面的第三行。iconst_5。那5个?它是硬编码的!!

不再保留对BatchOConstants的引用。让我们来测试一下:

在命令行上:

代码语言:javascript
运行
复制
> rm BatchOConstants.class
> java UserOfBatch
5

哇。代码运行,尽管它缺少应该提供5的类文件,从而证明它是由编译器本身而不是运行时“硬编码”的。

另一种“观察”任何值的CTC的方法是annoparams。注释参数必须通过硬编码到类文件中,所以不能传递表达式。给定:

代码语言:javascript
运行
复制
public @interface Foo {
    long value();

我不能写:@Foo(System.currentTimeMillis()),因为System.cTM显然不是一个编译时间常数。但是我可以编写@Foo(SomeClass.SOME_STATIC_FINAL_LONG_FIELD),假设分配给S_S_F_L_F的值是一个编译时间常数。如果不是,@Foo(...)代码将无法编译。如果是,它将进行编译: CTC-ness现在确定您的代码是否编译。

对于编译器何时被允许解释为“编译时间常量”并进行内联狂欢,有特定的规则。例如,null永远不是内联常量。过于简单化,但是:

对于字段,字段必须是静态的和最终的,并且具有原语或字符串类型,在现场初始化(在同一时间内,而不是在静态块中的后面),使用常量表达式,即不为

  • 对于局部变量,规则非常相似,不同之处在于,显然不需要它们是静态的,因为它们不能是。除此之外,所有的修复都适用:最终的、原始的或字符串的、非null的、在现场初始化的、使用常量表达式的。

不幸的是,局域的CTC-ness更难直接观察。不过,您编写的代码是间接观察它的好方法。你已经用你的指纹证明了firstName是CTC而lastName不是。

但我们可以观察到一些精选的东西。因此,让我们将您的代码进行编译,并将其放在javap中,以见证结果。Via Jonas Konrad's online javap tool,让我们分析一下:

代码语言:javascript
运行
复制
    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!");
    }

相关部分:

代码语言:javascript
运行
复制
         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

注意“start local 2”(即c;javap从0开始计数)显示了如何将hello!作为一个完整的常量加载,但“start local 3”(即d)显示了加载2个常量并调用makeConcat将它们绑定在一起。

票数 1
EN

Stack Overflow用户

发布于 2020-12-13 23:41:32

在编译代码后运行javap -c Test

你会看到的

代码语言:javascript
运行
复制
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                                                                                                              
}                                                                                                                             

如你所见

代码语言:javascript
运行
复制
 4: invokedynamic #3,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;

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

where as on

代码语言:javascript
运行
复制
10: ldc           #4                  // String JohnSmith

代码语言:javascript
运行
复制
14: ldc           #4                  // String JohnSmith 

这是编译器在执行firstname + "Smith""John" + "Smith"时生成的字节码,这意味着两者实际上都是从常量池中读取的。

这就是为什么当你使用==比较name1和name2时,它会返回false。因为name2name3引用常量池中的相同字符串。如果与==比较,则返回true。这就是为什么将2字符串与==进行比较并不是一个好主意。进行字符串比较时请使用String.equals()

因为两者都

票数 1
EN

Stack Overflow用户

发布于 2020-12-13 23:30:45

让我们用final查看字节码

代码语言:javascript
运行
复制
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的字节码

代码语言:javascript
运行
复制
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是在运行时动态计算的。但是,name2name3都是字符串池中的常量,这就是name2 == name3true的原因。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65276561

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档