我正在与下面的同事谜语挣扎:
public class App1 {
public static void main(String[] args) {
String s1 = "Ja".concat("va"); // seems to be interned?!
String s2 = s1.intern();
System.out.println(s1 == s2); // true
}
}
输出 true 。我有点惊讶,因为看起来s1
实习。但这不是常数表达,不是吗?
但后来我更加惊讶为什么以下打印 false 。
public class App2 {
public static void main(String[] args) {
String s1 = "Ja".concat("va"); // seems not to be interned?!
String s3 = new String("Java"); // this changes output
String s2 = s1.intern();
System.out.println(s1 == s2); // false
}
}
为什么引入s3
会改变输出?
答案 0 :(得分:6)
以下是管理Java String对象的规则,以及字符串池:
让我们回顾一下你的例子,
String s1 = "Ja".concat("va");
如果您查看字符串源代码中的concat
操作,您会注意到它最后会调用new
运算符。
new String(buf, true)
因此,s1未添加到字符串池中。
现在,让我们看看调用intern
的行
String s2 = s1.intern();
这里,intern
上的s1
方法返回String池中的对象(如果它不存在则创建)。因此,s2
包含String池中的对象。
同时,s1
仍然包含旧对象,而不是池中的对象。因此,
(s1 == s2)
总是会返回false
。
Java 1.8.0_92-b14中的修改行为
Java 8中的行为已更改。 Java编译器正在执行优化。如果在intern
之后立即调用concat
方法,Java 8将优化并在字符串池中创建字符串对象,并忽略我们在早期版本中见过的new
的早期行为。 Java的。请检查反编译代码的操作码中的优化(checkOne是App1,checkTwo是App2),
public static void checkOne(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=2, args_size=0 0: ldc #2 // String Ja 2: ldc #3 // String va 4: invokevirtual #4 // Method java/lang/String.concat:(Ljava/lang/String;)Ljava/lang/String; 7: astore_0 8: aload_0 9: invokevirtual #5 // Method java/lang/String.intern:()Ljava/lang/String; 12: astore_1 13: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 16: aload_0 17: aload_1 18: if_acmpne 25 21: iconst_1 22: goto 26 25: iconst_0 26: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V 29: return LineNumberTable: line 6: 0 line 7: 8 line 9: 13 line 10: 29 LocalVariableTable: Start Length Slot Name Signature 8 22 0 s1 Ljava/lang/String; 13 17 1 s2 Ljava/lang/String; StackMapTable: number_of_entries = 2 frame_type = 255 /* full_frame */ offset_delta = 25 locals = [ class java/lang/String, class java/lang/String ] stack = [ class java/io/PrintStream ] frame_type = 255 /* full_frame */ offset_delta = 0 locals = [ class java/lang/String, class java/lang/String ] stack = [ class java/io/PrintStream, int ] public static void checkTwo(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=3, args_size=0 0: ldc #2 // String Ja 2: ldc #3 // String va 4: invokevirtual #4 // Method java/lang/String.concat:(Ljava/lang/String;)Ljava/lang/String; 7: astore_0 8: new #8 // class java/lang/String 11: dup 12: ldc #9 // String Java 14: invokespecial #10 // Method java/lang/String."":(Ljava/lang/String;)V 17: astore_1 18: aload_0 19: invokevirtual #5 // Method java/lang/String.intern:()Ljava/lang/String; 22: astore_2 23: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 26: aload_0 27: aload_2 28: if_acmpne 35 31: iconst_1 32: goto 36 35: iconst_0 36: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V 39: return LineNumberTable: line 13: 0 line 14: 8 line 15: 18 line 17: 23 line 18: 39 LocalVariableTable: Start Length Slot Name Signature 8 32 0 s1 Ljava/lang/String; 18 22 1 s3 Ljava/lang/String; 23 17 2 s2 Ljava/lang/String; StackMapTable: number_of_entries = 2 frame_type = 255 /* full_frame */ offset_delta = 35 locals = [ class java/lang/String, class java/lang/String, class java/lang/String ] stack = [ class java/io/PrintStream ] frame_type = 255 /* full_frame */ offset_delta = 0 locals = [ class java/lang/String, class java/lang/String, class java/lang/String ] stack = [ class java/io/PrintStream, int ]
答案 1 :(得分:2)
intern
方法的文档说明保证从唯一字符串池中返回一个字符串。它并不能保证它与您实习的内容完全相同。
执行String s3 = new String("Java")
时,实际上是在传递给传递给构造函数的字符串文字"Java"
。这使s1.intern()
返回该字符串而不是s1
。
答案 2 :(得分:2)
我在JDK 1.8.0_144上。我运行你的程序,它在你描述的两个场景中打印'false'。它完全有道理。这就是原因。
执行语句"Ja".concat("va");
时,它返回new String object
。以下是return
方法的java.lang.String#concat
语句:
return new String(buf, true);
由于返回的String是使用new
关键字创建的,因此字符串不会添加到字符串池中。 (请记住,只有String literals和由常量表达式生成的String被添加到池中;使用new
创建的字符串不是)。
当您将s2创建为s1的实习生时,它是第一次将字符串“Java”添加到池中。所以在那一点上,s1和s2是不同的对象。字符串s1位于perm gen区域,而s2位于堆的主要部分的String池中。因此,它们在记忆方面并不相同。所以它正确打印错误。
行String s3 = new String("Java");
的引入与此行为无关。
答案 3 :(得分:0)
在App1中,连接的s1
是第一个" Java"要获得实习的字符串,在App2中它是显式文字" Java"得到实习。让我们详细了解一下:
让我们假设没有字符串" Java"在运行main()
之前的池中,[对于每个JRE版本可能都不是这样]。
String s1 = "Ja".concat("va");
现在有一个String" Java",不在池中。
String s2 = s1.intern();
现在," Java"在您的情况下,只需注册您的s1
实例作为池的一部分即可实现。因此,s1
和s2
相同。
该行
String s3 = new String("Java");
更改初始状态。你在这里有一个字符串文字" Java"它成为您的类文件的一部分,因此在加载App2类时会被插入到池中。
String s1 = "Ja".concat("va");
这仍然提供了一个新的" Java"池外的字符串,如App1中那样。
String s2 = s1.intern();
这找到了一个" Java"字符串已经存在于池中(String literal" Java"来自加载s3赋值语句),这是一个不同的实例。因此s1和s2现在不同了,s1是连接字符串,s2是实习文字。
如果App2类中的任何地方存在字符串文字或常量表达式,那么同样的情况也会发生。" Java"占据了#Java; Java"在你实习你的连锁字符串之前放在池中。
字符串是" Java"使案例变得棘手,因为在App类可能已经包含文字" Java"之前加载一些JRE类有点可能,这解释了为什么不同JRE的行为不同。尝试类似" hmkjlfhvjkh"。
答案 4 :(得分:0)
让我们分解。
String s3 = new String("Java");
s3
总是新的Object
;它永远不会从实习生池中获得
但是,从实习池(根据JLS)传递给其构造函数的String常量"Java"
是。
String s2 = s1.intern();
根据javadoc,如果字符串已经不在池中,intern()
会将其添加到池中并返回它,否则它将返回已存在的字符串。< / p>
String s1 = "Ja".concat("va");
这会创建一个新的对象"Java"
,而根本不涉及该池。
因此,如果在调用'"Java"
之前遇到字符串常量s1.intern()
,则将返回字符串常量。否则,将返回s1
。
答案 5 :(得分:-2)
引用intern(强调我的)的文档:
当调用
intern
方法时,如果池已经包含等于String
方法确定的equals(Object)
对象的字符串,则返回池中的字符串。 否则,此String
对象将添加到池中,并返回对此String
对象的引用。