接触JAVA已有2年多了,工作也1年了,本以为自己对JAVA的一些常用知识已经够了解了,但是一个高手(他的博客地址http://blog.csdn.net/xieyuooo,有空的话,可以去看看他的技术文章)在技术群里甩了几道关于String的题目,说大家可以做着玩玩,看看自己对String了解多少,不许猜答案,每道题都要说出理由。咋看之下,我懵了,发现自己原来对String的理解,只是点皮毛,下面我把那几道题贴出来,如果你全对了,并且有自己的理由,那么你不需要看本文章了。本人写这文章的目的,只是为了整理下自己对String的认识,下面的一些说法也许是错误的,呵呵,希望牛人留言指出。
//题目1:
public static void test1() {
String a = "a1";
String b = "a" + 1;
System.out.println(a == b);
}
//题目2:
public static void test2() {
String a = "ab";
String b = "b";
String c = "a" + b;
System.out.println(a == c);
}
//题目3:
public static void test3() {
final String a = "a";
String s1 = "ab";
final String b = "b";
String s2 = a + b;
System.out.println(s1 == s2);
}
//题目4:
private static String getBB() {return "b";}
public static void test4() {
String s1 = "ab";
final String b = getBB();
String s2 = "a" + b;
System.out.println(s1 == s2);
}
//题目5:
private final static String AB = "ab";
public static void test5() {
String a = "a";
String b = "b";
String c = a + b;
System.out.println("test5-=======>");
System.out.println(AB == c);
System.out.println(AB == c.intern());
}
//题目6:
private final static String AB2 = new String("ab");
public static void test6() {
String a = "a";
String b = "b";
String c = a + b;
System.out.println("test6-=======>");
System.out.println(AB2 == c);
System.out.println(AB2 == c.intern());
System.out.println(AB2.intern() == c.intern());
}
这几道题,我错了几道。其实做的时候,是瞎猜的,呵呵,因为自己还真的找不到理由。后来通过百度以及询问高手,自己才稍微明白,因为这些理由涉及到编译期,运行期,JVM的一些知识,而本人平时只是单纯的用JAVA,从来没研究过编译期,运行期,JVM这些东东,所以说,虽然通过百度知道了一点理由,但还不能说完全明白。好了,不说废话了。
注:下面说的常量池都是默认字符串常量池STRING_CONSTANT
在JAVA中是不允许对运算符进行重载的,但是String是唯一的特列,它对+和+=进行了重载。
我们都知道,JAVA文件经过编译后产生class文件,然后JVM加载class文件进行进一步解析。编译器把JAVA文件进行编译产生class文件,这期间称为编译期;JVM加载class文件进行解析,把相关信息放入内存空间等等,可以称为运行期。对于String来说,如果在编译期,就能确定其内容的,则会把对象放入常量池(位于方法区中Method Area),并返回该对象在常量池中的首地址,也就是引用值,比如说String s="a",则"a"会放入常量池中,并返回引用值给s,所以说s是指向常量池中"a"对象的;如果是String s=new String("a")这种格式,凡是有了new实例化一个对象,则该对象都是位于实例池中(位于堆Heap中),所以"a"不会放入常量池(当然,如果你用intern处理,"a"也可以放入常量池,后面再说),所以s是指向实例池中的"a",有了这点理解,那对于上面的几道题,就基本OK了。
//题目1:
public static void test1() {
String a = "a1";
String b = "a" + 1;
System.out.println(a == b);
}
题目1的运行结果:true
个人理解:a毫无疑问会指向常量池中的"a1"对象。那么b呢?如果a==b成立,也就是说b也是指向常量池中的"a1"对象了?但是b="a"+1?呵呵,这主要是编译器的功劳,如果是常量对象相加,编译器对java文件编译时,会把他们的内容合并(对于String来说,个人认为,常量池内的常量对象相加得到的内容也会放入常量池,注意如果是指向常量池的对象(或者说引用,如String x="a",x就是一个引用,也可以说对象,只能指向String类的对象)相加,是不会放入常量池的,而是位于实例池(如果用final修饰的话,也是可以放入常量池的)。
下面来看看,对于题目1编译后是什么样子。
Compiled from "Test.java"
public class test.Test extends java.lang.Object{
public test.Test();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return
public static void test1();
Code:
0: ldc #15; //String a1
2: astore_0
3: ldc #15; //String a1
5: astore_1
6: getstatic #17; //Field java/lang/System.out:Ljava/io/PrintStream;
9: aload_0
10: aload_1
11: if_acmpne 18
14: iconst_1
15: goto 19
18: iconst_0
19: invokevirtual #23; //Method java/io/PrintStream.println:(Z)V
22: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #37; //Method test1:()V
3: return
}
注意看,test1()方法中的0,3两行,发现是一样的,也就是说String b="a"+1,经过编译器编译后,变成String b="a1"了,所以a==b是成立的。
public static void test2() {
String a = "ab";
String b = "b";
String c = "a" + b;
System.out.println(a == c);
}
题目2运行结果:false
个人见解:String c="a"+b,由于b不是一个常量String对象,所以编译是,编译期时不会把"a"和对象b指向的内容"b"进行相加的,这个c只有在运行期才能确定它指向的对象,这样一来,c指向的是实例池中"ab"对象,而a是执行常量池中的"ab"对象,所以a==c是不成立的。
下面看看题目2编译后的内容:
Compiled from "Test.java"
public class test.Test extends java.lang.Object{
public test.Test();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return
public static void test2();
Code:
0: ldc #15; //String ab
2: astore_0
3: ldc #17; //String b
5: astore_1
6: new #19; //class java/lang/StringBuilder
9: dup
10: ldc #21; //String a
12: invokespecial #23; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
15: aload_1
16: invokevirtual #26; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #30; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_2
23: getstatic #34; //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 #40; //Method java/io/PrintStream.println:(Z)V
39: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #53; //Method test2:()V
3: return
}
看test2()方法中的0,6两行,发现是不一样,第6行有一个new,说明c是通过new出来的,而且还不是new String,而是new StringBuilder。
在这可以发现运行期,对于多个String对象相加,JVM的处理是通过new StringBuilder来处理的,如String c="a"+b;则会类似这样String c=new StringBuilder().append("a").append(b).toString();
public static void test3() {
final String a = "a";
String s1 = "ab";
final String b = "b";
String s2 = a + b;
System.out.println(s1 == s2);
}
题目3运行结果:true
个人见解:s1会指向常量池中的"ab"对象,这个没有疑问,那s2为什么也会指向常量池中的"ab"对象呢?因为对象a,b都有final修饰,并且a,b都指向常量池,所以对于String s2=a+b,编译器在编译优化时,可以确定s2的内容了,所以s1==s2。
看看题目3编译后的样子:
Compiled from "Test.java"
public class test.Test extends java.lang.Object{
public test.Test();
Code:
0: aload_0
1: invokespecial #8; //Method java/lang/Object."<init>":()V
4: return
public static void test3();
Code:
0: ldc #15; //String a
2: astore_0
3: ldc #17; //String ab
5: astore_1
6: ldc #19; //String b
8: astore_2
9: ldc #17; //String ab
11: astore_3
12: getstatic #21; //Field java/lang/System.out:Ljava/io/PrintStream;
15: aload_1
16: aload_3
17: if_acmpne 24
20: iconst_1
21: goto 25
24: iconst_0
25: invokevirtual #27; //Method java/io/PrintStream.println:(Z)V
28: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #41; //Method test3:()V
3: return
}
看test3()方法中的3,9两行,是不是发现是一样的啊,呵呵猜测正确。
再来看看第4题,是不是发现很类似,呵呵
private static String getBB() {
return "b";
}
public static void test4() {
String s1 = "ab";
final String b = getBB();
String s2 = "a" + b;
System.out.println(s1 == s2);
}
第4题运行结果:false
个人见解:对象b虽然用了final修饰,但是其所指向的对象要等到运行期才能确定下来,这样一来在编译期就无法对String s2="a"+b进行优化了,所以s1指向常量池,s2指向实例池,不用多说了。
接下来看看第5题
//题目5:
private final static String AB = "ab";
public static void test5() {
String a = "a";
String b = "b";
String c = a + b;
System.out.println(AB == c);
System.out.println(AB == c.intern());
}
第5题运行结果:false
true
个人见解:毫无疑问,AB会指向常量池中的"ab",而c是指向实例池中的"ab",所以AB==c是不成立的。而对于AB==c.intern()。这个就涉及到intern的使用了。
c.intern()会把c指向的实例池中的对象内容和常量池(字符串常量池)中对象的内容进行比较,如果常量池中没有,则把c指向实例池中的对象复制到常量池中,并且返回该对象在常量池中的首地址(引用值);如果常量池中有了,则直接返回引用值。所以AB==c.intern()就不用多说了。
对于第6题就不说了,只要注意引用是指向常量池还是实例池就OK了。
补充: 记得好像有道面试题:String s=new String("abc");创建了几个对象。我的答案是1个或者2个。new String("abc")对象时,会先到常量池中寻找,常量池中是否已有"abc"对象(equals比较),如果常量池中没有,则会现在常量池中创建一个"abc"对象,然后在实例池(堆中)再创建常量池中该对象的拷贝对象,所以共创建了2个对象;如果常量池中已有"abc"对象,则new String("acb")时,则直接在实例池中创建常量池中的“abc"对象的拷贝对象。 |