java中的intern()⽅法
前⾔
最近遇到⼀个Intern()⽅法,代码如下,在jdk1.8的环境下得到如下的测试结果,给我整不会了,因此研究了⼀下这个⽅法,记录⼀下:
st;
2
3/**
4 * @description:
5 * @author: luguilin
6 * @date: 2022-02-25 11:14
7 **/
8public class TestString {
9static void test01(){
10        String s1 = new String("1")+new String("23");
11        s1.intern();
12        String s2 = "123";
13        System.out.println( s1 == s2);//true
14    }
15
16static void test02(){
17        String s1 = new String("1")+new String("23");
18        String s2 = "123";
19        s1.intern();
20        System.out.println( s1 == s2); //false
21    }
22
23static void test03(){
24        String s1 = new String("1")+new String("23");
25        String s2 = "123";
26        System.out.println( s1 == s2);//false
27        s1.intern();
28        System.out.println( s1 == s2);//false
29        s1 = s1.intern();
30        System.out.println( s1 == s2);//true
31    }
32
33public static void main(String[] args) {
34        test01();
35        System.out.println("-----------------");
36        test02();
37        System.out.println("-----------------");
38        test03();
39    }
40 }
不说别的,上述⽅法中的test01(),为什么是True?
在我之前的印象⾥,s2指向⽅法区中的常量,s1应该指向的是堆上的对象,⼆者应该是不⼀样的啊?为什么是对的?
⽽我调换了⼀下s1.intern()语句的顺序以后,就⼜是false了。当我s1=s1.intern()以后,⼜相等了。这下彻底给我搞蒙了。
1、java的Intern()⽅法
在jdk1.8中,intern⽅法的定义在Java的String类中是这样定义的,是⼀个本地⽅法,其中源码由C实现
public native String intern();
再来看⼀下源码的注释描述:
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
(直译:当调⽤ intern ⽅法时,如果池中已经包含⼀个与该⽅法确定的对象相等的字符串,则返回池中的字符串。否则,将此对象添加到池中并返回对该对象的引⽤。)
翻译过来的意思就是:如果常量池中已经有了此字符串,那么将常量池中该字符串的引⽤返回,如果没有,那么将该字符串对象添加到常量池中,并且将引⽤返回。
⾸先要明⽩,这⾥注释的该字符串是调⽤此⽅法的字符串,返回的是引⽤。
代码如下:在jdk1.8中运⾏
st02;
2
3
4public class TestIntern {
5void test01(){
6        String s1 = new String("xyz");
7        String s2 = "xyz";
8        System.out.println(s1==s2); // false
9    }
字符串常量池存的是实例还是引用?10
11void test02(){
12        String s2 = "xyz";
13        String s1 = new String("xyz");
14        System.out.println(s1==s2); // false
15    }
16
17public static void main(String[] args) {
18        TestIntern ins = new TestIntern();
19        st01();
20        st02();
21    }
22 }
2、new String("xyz")会创建⼏个对象?
动⼿实践后,发现再new String("xyz")有可能会创建⼀个(不好验证,但是可以通过下⽂的分析得出结论),也有可能会创建两个(可验证)。
结论:如果常量池中没有 xyz,那么就会创建两个,现在堆中创建⼀个,然后将对象copy到常量池中,也就是第⼆次创建,堆中和常量池中是两个对象。
事实上,在不同的jdk版本中,intern()⽅法的实现是不⼀样的,主要原因是永久代的去除和元空间的增加,见《》和《》。
Java6版本:
intern⽅法作⽤:确实如上述注释上所描述,如果常量池中没有字符串,则将该字符串对象加⼊常量池,并返回引⽤。
  ** 这⾥需要注意:Java6中常量池是在⽅法区中,⽽Java1.6版本hotspot采⽤永久带实现了⽅法区,永久代是和Java堆区分的,即就是常量池中没有字符串,那么将该字符串对象放⼊永久代的常量池中,并返回其引⽤。
Java7和Java8版本:
intern⽅法作⽤:和注释描述的并不同,
  如果常量池有,那么返回该字符串的引⽤。
  如果常量池没有,那么如果是"a".intern调⽤,那么就会把"a"放⼊常量池,并返回"a"在常量池中的引⽤。
          如果是new String("a").internal ,其中在 new String的时候上⽂已经说到过,会在堆和常量池各创建⼀个对象,那么这⾥返回的就是常量池的字符串a的引⽤。
          如果是new StringBuilder("a").internal,其中new StringBuilder会在堆中创建⼀个对象,常量池没有,这⾥调⽤intern ⽅法后,**会将堆中字串a的引⽤放到常量池,注意这⾥始终只是创建了⼀个对象,
                返回的引⽤虽然是常量池的,但是常量池的引⽤是指向堆中字串a的引⽤的。
再简单总结⼀下Java7和Java8的intern⽅法作⽤:
如果常量池没有,那么会将堆中的字符串的引⽤放到常量池,注意是引⽤,然后返回该引⽤。为什么Java7和Java8会不⼀样呢,原因就是 Java7之后(部分虚拟机,Hotspot,JRockit)已经将永久代的常量池、静态变量移出,放⼊了Java堆中,⽽永久代也在Java8中完全废弃,⽅法区改名为元空间。
既然常量池已经在Java6之后放⼊了堆中,那么如果堆中已经创建过此字符串的对象了,那么就没有必要在常量池中再创建⼀个⼀⽑⼀
样的对象了,直接将其引⽤拷贝返回就好了,因为都是处于同⼀个区域Java堆中。
3、实践验证
3.1 实际验证⼀下上述结论
st02;
2
3
4public class TestIntern {
5
6/**
7    * 在new的时候已经创建了两个对象,第⼆⾏,只是获取的第⼀⾏创建的常量池的对象的引⽤,实际的对象已经创建过了。
8    * 这⾥是两个不同的对象,返回false。
9*/
10void test01() {
11        String s1 = new String("xyz");
12        String s2 = "xyz";
13        System.out.println(s1 == s2); // false
14    }
15
16/**
17    * 和上述⼀样,只不过这⼀次第⼀⾏,现在常量池创建了对象,第⼆⾏发现常量池已经有了,只在堆上创建了⼀次对象.
18    * 但仍然是两个对象,引⽤不同,返回false。
19*/
20void test02() {
21        String s2 = "xyz";
22        String s1 = new String("xyz");
23        System.out.println(s1 == s2); // false
24    }
25
26/**
27    * 第⼀⾏,StringBuilder只会在堆中创建⼀个对象,第⼆⾏调⽤intern⽅法后,会将堆中的引⽤放到到常量池中。
28    * 第三⾏发现常量池中已经有这个字符串的引⽤了,直接返回。
29    * 因此是同⼀个引⽤,返回的都是第⼀次创建的堆中字串的引⽤
30*/
31void test03() {
32        StringBuilder s1 = new StringBuilder("xyz");
33        String s2 = s1.toString().intern();
34        String s3 = "xyz";
35        System.out.println(s2 == s3); // true
36    }
37
38/**
39    * 和上述3的不同之处在于没有调⽤intern⽅法,因此结果输出不⼀样。
40*/
41void test04() {
42        StringBuilder s1 = new StringBuilder("xyz");
43        String s2 = s1.toString();
44        String s3 = "xyz";
45        System.out.println(s2 == s3); // false
46    }
47
48/**
49    * new String之后使⽤ + 在Java中会进⾏编译优化,编译成字节码指令后,会将 + 优化成先new Stringbuilder对象,然后调⽤append⽅法进⾏拼接。
50    * 因此这⾥s1最终创建的时候,xyzz字符串并没有在常量池创建,只是在堆中创建了,因为就如同上⾯的test03()⼀样,是new Stringbuilder操作。
51    * 所以在调⽤intern操作后,将其堆中的引⽤放⼊常量池并返回。
52    * 所以后⾯的结果都是true,因为⾄始⾄终都是堆中的⼀个对象。
53*/
54void test05() {
55        String s1 = new String("xyz") + new String("z");
56        String s2 = s1.intern();
57        String s3 = "xyzz";
58        System.out.println(s1 == s2); // true
59        System.out.println(s1 == s3); // true
60        System.out.println(s2 == s3); // true
61    }
62
63/**
64    * 和上述test05()是相反的,结果输出也不同。
65*/
66void test06() {
67        String s1 = new String("xyz") + new String("z");
68        String s3 = "xyzz";
69        System.out.println(s1 == s3); // false
70    }
71
72/**
73    * s1指向的对象并没有改变
74    * s2指向常量区,s1指向堆,所以不⼀样
75*/
76void test07() {
77        String s1 = new String("xyz") + new String("z");
78        String s2 = "xyzz";
79        s1.intern();
80        System.out.println(s1 == s2); // false
81    }
82
83/**
84    * s1.intern()之后,在常量区添加了堆中"xyzz"的引⽤,s2指向了这个常量池中"xyzz"对象
85    * 因此⼆者不相等
86*/
87void test08() {
88        String s1 = new String("xyz") + new String("z");
89        s1.intern();
90        String s2 = "xyzz";
91        System.out.println(s1 == s2); // false
92    }
93
94/**
95    * 第⼀个判断,
96    * s1.intern()之后,在常量区添加了堆中"xyzz"的引⽤
97    * s2也指向了常量池中这个引⽤,但是s1本⾝没有变,指的是堆中对象的引⽤,因此不相等
98    * <p>
99    * 第⼆个判断,
100    * s1 = s1.intern()以后,s1也指向了常量池中这个引⽤,因此相等
101*/
102void test09() {
103        String s1 = new String("xyz") + new String("z");
104        s1.intern();
105        String s2 = "xyzz";
106        System.out.println(s1 == s2); // false
107        s1 = s1.intern();
108        System.out.println(s1 == s2); // true
109    }
110
111
112public static void main(String[] args) {
113        TestIntern ins = new TestIntern();
114        st01();
115        st02();
116        st03();
117        st04();
118        st05();
119        st06();
120        st07();
121        st08();
122        st09();
123    }
124 }
3.2 另⼀个⾯试题
1public class Test {
2public static void main(String[] args) {
3        String str1 = new StringBuilder("计算机").append("软件").toString();
4        String str2 = str1.intern();
5        String str3 = new StringBuilder("ja").append("va").toString();
6        String str4 = str3.intern();
7        System.out.println(str1==str2);
8        System.out.println(str3==str4);
9    }
10 }
jdk1.8的输出答案是true和false。
jdk1.6的输出是两个false。
其他都好理解,为什么在1.8中,java这个变量,第⼆个判断,是false呢?这两代码不是⼀样么?理论上不应该是true么?分析
在jdk1.6中
intern⽅法会把⾸次遇到的字符串复制到⽅法区中,返回的也是⽅法区这个字符串的引⽤。
⽽由StringBuilder创建的字符串实例在Java堆上,所以必然不是⼀个引⽤,所以返回false
str1指向堆,str2指向⽅法区,所以返回结果返回false
同理,str3和str4的返回结果也为false
下来我们看⼀看jdk1.7的intern⽅法
  jdk1.7的intern⽅法不会在复制实例,只是在常量池中记录⾸次出现的实例引⽤。
  因此str2指向的引⽤其实就是str1指向Java堆中StringBuilder创建的字符串实例。所以返回结果为true
  但是java这个字符串常量在编译期就已经在⽅法区的常量池中了,不符合⾸次出现,所以str4指向的是常量池中的java字⾯量
  所以返回结果为false。
  问题⼜来了,java这个字⾯量为什么在编译期就出现在了常量池。我们可以进⼊System类中。看看有什么东西。
  进⼊System类之后,我们发现这⾥有⼀个Version.init⽅法
再次进去查看
再次进去查看
哇,这么多常量,包括java,版本号,此版本号,都已经加载到常量池中,所以当我们调⽤str3.intern()⽅法时,java字⾯量已经存在,不符合⾸次出现,所以返回false,同理,我们也可以试⼀试这⾥的字⾯量,发现返回都是false。
String类的⼀个intern⽅法,涉及到了Java堆,java运⾏时常量池,涉及⾯很⼴泛,如果你不了解,是不是很吃亏。
4、最开始的问题
看了这么多以后,我以为我搞明⽩了最开始的问题,我突然发现,我最开始的代码和是第三节中代码是不⼀样的,如下,每⼀个代码都是静态⽅法:
下⾯⼀些栗⼦,⾃⼰思考吧,我也糊涂了
st;
/**
* @description:
* @author: luguilin
* @date: 2022-02-25 11:14
**/
public class TestString {
static void test01(){
String s1 = new String("1")+new String("23");
String s2 = "123";
s1.intern();
System.out.println( s1 == s2); //false
}
static void test02(){
String s1 = new String("1")+new String("23");
s1.intern();
String s2 = "123";
System.out.println( s1 == s2);// false
}
void test03(){

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。