JAVA String的那点趣事

        接触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"对象的拷贝对象。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值