经过反编译看Java String及intern内幕--费元星站长

经过反编译看Java String及intern内幕

 

1、字符串问题html

  字符串在咱们平时的编码工做中其实用的很是多,而且用起来也比较简单,因此不多有人对其作特别深刻的研究。却是面试或者笔试的时候,每每会涉及比较深刻和难度大一点的问题。我在招聘的时候也偶尔会问应聘者相关的问题,倒不是说必定要回答的特别正确和深刻,一般问这些问题的目的有两个,第一是考察对 JAVA 基础知识的了解程度,第二是考察应聘者对技术的态度。java

  咱们看看如下程序会输出什么结果?若是你能正确的回答每一道题,而且清楚其缘由,那本文对你就没什么太大的意义。若是回答不正确或者不是很清楚其原理,那就仔细看看如下的分析,本文应该能帮助你清楚的理解每段程序的结果及输出该结果的深层次缘由。面试

代码段一:app

1
2
3
4
5
6
7
8
9
10
11
package  com.paddx.test.string;
public  class  StringTest {
     public  static  void  main(String[] args) {
         String str1 =  "string" ;
         String str2 =  new  String( "string" );
         String str3 = str2.intern();
 
         System.out.println(str1==str2); //#1
         System.out.println(str1==str3); //#2
     }
}

 代码段二:post

1
package  com.paddx.test.string;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public  class  StringTest01 {
     public  static  void  main(String[] args) {
         String baseStr =  "baseStr" ;
         final  String baseFinalStr =  "baseStr" ;
 
         String str1 =  "baseStr01" ;
         String str2 =  "baseStr" + "01" ;
         String str3 = baseStr +  "01" ;
         String str4 = baseFinalStr+ "01" ;
         String str5 =  new  String( "baseStr01" ).intern();
 
         System.out.println(str1 == str2); //#3
         System.out.println(str1 == str3); //#4
         System.out.println(str1 == str4); //#5
         System.out.println(str1 == str5); //#6
     }
}

代码段三(1): 字体

1
2
3
4
5
6
7
8
9
10
package  com.paddx.test.string;<br>  
public  class  InternTest {
     public  static  void  main(String[] args) {
 
         String str2 =  new  String( "str" )+ new  String( "01" );
         str2.intern();
         String str1 =  "str01" ;
         System.out.println(str2==str1); //#7
     }
}

 代码段三(2):ui

1
2
3
4
5
6
7
8
9
10
package  com.paddx.test.string;
 
public  class  InternTest01 {
     public  static  void  main(String[] args) {
         String str1 =  "str01" ;
         String str2 =  new  String( "str" )+ new  String( "01" );
         str2.intern();
         System.out.println(str2 == str1); //#8
     }
}

 为了方便描述,我对上述代码的输出结果由#1~#8进行了编码,下文中蓝色字体部分即为结果。编码

2、字符串深刻分析url

  一、代码段一分析spa

  字符串不属于基本类型,可是能够像基本类型同样,直接经过字面量赋值,固然也能够经过new来生成一个字符串对象。不过经过字面量赋值的方式和new的方式生成字符串有本质的区别:

  经过字面量赋值建立字符串时,会优先在常量池中查找是否已经存在相同的字符串,假若已经存在,栈中的引用直接指向该字符串;假若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。而经过new的方式建立字符串时,就直接在堆中生成一个字符串的对象(备注,JDK 7 之后,HotSpot 已将常量池从永久代转移到了堆中。详细信息可参考《JDK8内存模型-消失的PermGen》一文),栈中的引用指向该对象。对于堆中的字符串对象,能够经过 intern() 方法来将字符串添加的常量池中,并返回指向该常量的引用。

如今咱们应该能很清楚代码段一的结果了:

结果 #1:由于str1指向的是字符串中的常量,str2是在堆中生成的对象,因此str1==str2返回false。

结果 #2:str2调用intern方法,会将str2中值(“string”)复制到常量池中,可是常量池中已经存在该字符串(即str1指向的字符串),因此直接返回该字符串的引用,所以str3==str2返回true。

如下运行代码段一的代码的结果:

 二、代码段二分析

对于代码段二的结果,仍是经过反编译StringTest01.class文件比较容易理解:

 常量池内容(部分):

执行指令(部分,第二列#+序数对应常量池中的项):

在解释上述执行过程以前,先了解两条指令:

ldc:Push item from run-time constant pool,从常量池中加载指定项的引用到栈。

astore_<n>:Store reference into local variable,将引用赋值给第n个局部变量。

如今咱们开始解释代码段二的执行过程:

0: ldc           #2:加载常量池中的第二项("baseStr")到栈中。

2: astore_1      :将1中的引用赋值给第一个局部变量,即String baseStr = "baseStr";

3: ldc           #2:加载常量池中的第二项("baseStr")到栈中。

5: astore_2      :将3中的引用赋值给第二个局部变量,即 final String baseFinalStr="baseStr"; 

6: ldc           #3:加载常量池中的第三项("baseStr01")到栈中。

8: astore_3     :将6中的引用赋值给第三个局部变量,即String str1="baseStr01";

9: ldc           #3:加载常量池中的第三项("baseStr01")到栈中。

11: astore        4:将9中的引用赋值给第四个局部变量:即String str2="baseStr01";

结果#3:str1==str2 确定会返回true,由于str1和str2都指向常量池中的同一引用地址。因此其实在JAVA 1.6以后,常量字符串的“+”操做,编译阶段直接会合成为一个字符串。

13: new           #4:生成StringBuilder的实例。

16: dup        :复制13生成对象的引用并压入栈中。

17: invokespecial #5:调用常量池中的第五项,即StringBuilder.<init>方法。

以上三条指令的做用是生成一个StringBuilder的对象。

20: aload_1  :加载第一个参数的值,即"baseStr"

21: invokevirtual #6 :调用StringBuilder对象的append方法。

24: ldc           #7:加载常量池中的第七项("01")到栈中。

26: invokevirtual #6:调用StringBuilder.append方法。

29: invokevirtual #8:调用StringBuilder.toString方法。

32: astore        5:将29中的结果引用赋值改第五个局部变量,即对变量str3的赋值。

结果 #4:由于str3其实是stringBuilder.append()生成的结果,因此与str1不相等,结果返回false。

34: ldc           #3:加载常量池中的第三项("baseStr01")到栈中。

36: astore        6:将34中的引用赋值给第六个局部变量,即str4="baseStr01";

结果 #5 :由于str1和str4指向的都是常量池中的第三项,因此str1==str4返回true。这里咱们还能发现一个现象,对于final字段,编译期直接进行了常量替换,而对于非final字段则是在运行期进行赋值处理的。

38: new           #9:建立String对象

41: dup               :复制引用并压如栈中。

42: ldc           #3:加载常量池中的第三项("baseStr01")到栈中。

44: invokespecial #10:调用String."<init>"方法,并传42步骤中的引用做为参数传入该方法。

47: invokevirtual #11:调用String.intern方法。

从38到41的对应的源码就是new String("baseStr01").intern()。

50: astore        7:将47步返回的结果赋值给变量7,即str5指向baseStr01在常量池中的位置。

结果 #6 :由于str5和str1都指向的都是常量池中的同一个字符串,因此str1==str5返回true。

运行代码段二,输出结果以下: 

 三、代码段三解析:

 对于代码段三,在 JDK 1.6 和 JDK 1.7中的运行结果不一样。咱们先看一下运行结果,而后再来解释其缘由:

 JDK 1.6 下的运行结果:

JDK 1.7 下的运行结果:

 

根据对代码段一的分析,应该能够很简单得出 JDK 1.6 的结果,由于 str2 和 str1原本就是指向不一样的位置,理应返回false。

比较奇怪的问题在于JDK 1.7后,对于第一种状况返回true,可是调换了一下位置返回的结果就变成了false。这个缘由主要是从JDK 1.7后,HotSpot 将常量池从永久代移到了元空间,正由于如此,JDK 1.7 后的intern方法在实现上发生了比较大的改变,JDK 1.7后,intern方法仍是会先去查询常量池中是否有已经存在,若是存在,则返回常量池中的引用,这一点与以前没有区别,区别在于,若是在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。因此:

结果 #7:在第一种状况下,由于常量池中没有“str01”这个字符串,因此会在常量池中生成一个对堆中的“str01”的引用,而在进行字面量赋值的时候,常量池中已经存在,因此直接返回该引用便可,所以str1和str2都指向堆中的字符串,返回true。

结果 #8:调换位置之后,由于在进行字面量赋值(String str1 = "str01")的时候,常量池中不存在,因此str1指向的常量池中的位置,而str2指向的是堆中的对象,再进行intern方法时,对str1和str2已经没有影响了,因此返回false。

 

3、常见面试题解答

有了对以上的知识的了解,咱们如今再来看常见的面试或笔试题就很简单了:

 

Q:String s = new String("xyz"),建立了几个String Object? 

A:两个,常量池中的"xyz"和堆中对象。

 

Q:下列程序的输出结果:

String s1 = “abc”;
String s2 = “abc”;
System.out.println(s1 == s2);

A:true,均指向常量池中对象。

 

Q:下列程序的输出结果:

String s1 = new String(“abc”);
String s2 = new String(“abc”);
System.out.println(s1 == s2);

A:false,两个引用指向堆中的不一样对象。

 

Q:下列程序的输出结果:

String s1 = “abc”;
String s2 = “a”;
String s3 = “bc”;
String s4 = s2 + s3;
System.out.println(s1 == s4);

A:false,由于s2+s3其实是使用StringBuilder.append来完成,会生成不一样的对象。

 

Q:下列程序的输出结果:

String s1 = “abc”;
final String s2 = “a”;
final String s3 = “bc”;
String s4 = s2 + s3;
System.out.println(s1 == s4);

A:true,由于final变量在编译后会直接替换成对应的值,因此实际上等于s4="a"+"bc",而这种状况下,编译器会直接合并为s4="abc",因此最终s1==s4。

 

Q:下列程序的输出结果:

String s = new String("abc");
String s1 = "abc";
String s2 = new String("abc");

System.out.println(s == s1.intern());
System.out.println(s == s2.intern());
System.out.println(s1 == s2.intern());

A:false,false,true,具体缘由参考第二部份内容。

相关文章
相关标签/搜索