要说清楚intern的原理,还得先明白以下字面量、常量池的一些概念。
一 字面量
简单的说,编译时可以明确求值的字符串成为称为字面量,比如String a = "hello",a就是一个字面量。
而以下例子中s就不是一个字面量,因为s由a和b两个变量拼接而成;s1是由两个字面量拼接而成,也可以视为一个字面量;s2是通过new String的方式声明的字符串,也不是一个字面量。
String a = "hello";
String b = "world";
String s = a + b;
String s1 = "hello" + "world";
String s2 = new String("hello world");
二 Class文件的常量池(Constant Pool)
一个.java文件经过javac编译后生成一个对应的class文件,class文件中包含了版本号、类或者接口信息和常量池等。其中常量池用来存放编译期的字面量和符号引用,比如String a = "hello",a是一个符号引用,"hello"是一个字面量。
三 运行时数据区的字符串常量池
Java程序运行时的内存区域,称为运行时数据区。包括虚拟机栈、程序计数器、堆、元空间(永久代)等区域,具体可以参考jvm相关的知识,这里不做具体介绍。在jdk1.7之前,字符串常量池在永久代中,jdk1.7之后是在堆中。
编译期在class文件常量池中的字符串常量,在运行期就在字符串常量池中被创建。当然,这个创建并不是程序一开始执行就创建,而是在第一次使用到该字符串的时候才在常量池中创建。
四 intern的原理
intern方法的作用有两个,一是把调用该方法的字符串引用存入到运行时数据区的字符串常量池(如果常量池中不存在),二是返回字符串常量池中该引用地址。当创建一个新的字符串字面量时,如果该字符串已经在常量池中,就直接返回常量池中的引用。
举个例子,以下代码返回结果为true。具体分析如下:
1 声明了一个字面量"a",在编译期存储在class文件的常量池中,运行时在字符串常量池中创建。即,执行完1后,字符串常量池中有字符串"a"的引用。
2 同理,执行完1后,字符串常量池中有字符串"b"的引用。
3 第三行,s不是一个字面量,因为a和b是两个变量的形式参与拼接,所以结果"ab"不在字符串常量池中。
4 此时,intern的作用就可以发挥了。执行s.intern,就会把"ab"放入常量池中,并且返回其引用,也就是s这个引用(这里没有使用到)。
5 创建一个字面量"ab",因为"ab"已经存在常量池中,所以直接返回其引用,并赋值给s1。
因此s和s1实际上指向的地址是相同的。
String a = "a"; // 1
String b = "b"; // 2
String s = a + b; // 3
s.intern(); // 4
String s1 = "ab"; // 5
System.out.println(s == s1); // 6
最后,再来个例子练习一下吧~
String a = "a";
String b = "b";
String s = a + b;
s.intern();
String s1 = "ab";
String s2 = new String("ab");
String s3 = new String("a") + new String("b");
String s4 = "a" + "b";
System.out.println(s == s1); // true
System.out.println(s == s2); // false
System.out.println(s == s3); // false
System.out.println(s == s4); // true