intern
对象 A 在执行 intern 时,会检查字符串常量池中是否存在与对象 A 相同的对象 B。
- 存在。返回对象 B,此时引用的是对象 B。
- 不存在。将对象 A 加入常量池,并返回对象 A,此时引用的仍是对象 A。
关于字符串字面量。
-
在第一次出现字符串字面量时,就会直接加入字符串常量池。
-
对字符串字面量的引用都是来自字符串常量池的对象。
抓住以上关键点,便可轻松破解下面代码的奥秘。
代码
public class InternDemo {
public static void main(String[] args) {
String s1 = UUID.fromString("6d86f4d7-9bfc-47c8-897f-a2f5531bf368").toString();
// 此时第一次出现 "6d86f4d7-9bfc-47c8-897f-a2f5531bf368" 简称该串为 A,则 A 直接加入字符串常量池。所以后续 intern 返回的是 A。
// Java 中所有按照方法返回的除了 intern 外都是新的对象,所以此处 s1 引用的是新对象 B。
String s2 = "6d86f4d7-9bfc-47c8-897f-a2f5531bf368";
// 此处直接引用字符串常量池的 A。
System.out.println(s1.intern() == s2); // true
// s1.intern 返回的是 A,而 s2 引用的也是 A,所以是 true。
System.out.println(s1.intern() == s1); // false
// s1.intern 返回的是 A,而 s1 引用的是 B,所以是 false。
String s3 = UUID.randomUUID().toString();
// s3 引用的串必然是不存在常量池中的串,该串称之为 C
System.out.println(s3 == s3.intern()); //true
// s3 调用 intern 时,字符串常量池中没有与之相同的串,所以会将 C 加入字符串常量池,返回对象 C。
// 而 s3 也是对 C 的引用。两者引用相同所以是 true。
String s4 = new String("aaa");
// 此时第一次出现 "aaa" 简称该串 D,则 D 直接加入字符串常量池,后续 intern 返回的是 D。但此处 s4 引用的是新对象 E。
System.out.println(s4.intern() == s4); //false
// s4.intern 返回的是 D,而 s4 引用的是 E,所以是false。
String s5 = "abb";
// 此时第一次出现 "abb" 简称该串 F,则 F 直接加入字符串常量池,后续 intern 返回的是 F。
// 对字面量的引用直接来自字符串常量池,所以 s5 的也是对 F 的引用。
String s6 = s5.intern();
// 此时 s6 引用的是 s5.intern 返回的 F
System.out.println(s6 == s5); // true
// s5 与 s6 均引用 F,所以为 true
String s7 = new StringBuilder("123").append("2").toString();
// 第一出现 "123" 和 "2",这两个串加入字符串常量池。
// 但使用 toString 后的 "1232" G 并没有加入字符串常量池,所以 s7 引用的 G,不会被后续通过字面量以及 intern 再次引用。
String s8 = "123" + "2"; // 会被优化成 "1232",称该串为 H,H 加入字符串常量池,后续相同的串调用 intern 会返回 H
System.out.println(s7.intern() == s7); // false
// s7 调用 intern 时会在字符串常量池中匹配到与 G 长的一样的 H,所以返回字符串常量池中的 H。而 s7 引用的是 G,所以为 false
// 而当 String s8 = "123" + "2"; 注释掉后。
// 调用 intern 时无法在字符串常量池中找到相同的 H,所以就会将 G 加入字符串常量池。
// 而此时 s7.intern 与 s7 都是引用自 G,所以会变成 true。
}
}
读者可自行检验。