接着分析String类:
我们知道字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。(类似于缓存)每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串。
1.String a = "str"; 2.String b = "str"; 3.String c = new String("str");
1和2中a、b字面上的str都是指向JVM字符串常量池中的"str"对象,他们指向同一个对象。3中new关键字一定会产生一个对象"str"同时这个对象是存储在堆中。c仅仅是这个对象的引用保存的是它在堆中的地址。
下面看一下测试代码:
分析:当执行String c="aaa"时,JVM首先会去字符串池中查找是否存在"aaa"这个对象,如果不存在,则在字符串池中创建"aaa"这个对象,然后将池中"aaa"这个对象的引用地址返回给字符串常量c,这样c会指向池中"aaa"这个字符串对象;如果存在,则不创建任何对象,直接将池中"aaa"这个对象的地址返回,赋给字符串常量。当创建字符串对象d时,字符串池中已经存在"aaa"这个对象,直接把对象"aaa"的引用地址返回给d,这样d指向了池中"aaa"这个对象,也就是说c和d指向了同一个对象,因此语句System.out.println(c == d)输出:true。
继续看代码:
分析: 采用new关键字新建一个字符串对象时,JVM首先在字符串池中查找有没有"aaa"这个字符串对象,如果有,则不在池中再去创建"aaa"这个对象了,直接在堆中创建一个"aaa"字符串对象,然后将堆中的这个"aaa"对象的地址返回赋给引用e,这样e就指向了堆中创建的这个"aaa"字符串对象;如果没有,则首先在字符串池中创建一个"aaa"字符串对象,然后再在堆中创建一个"aaa"字符串对象,然后将堆中这个"aaa"字符串对象的地址返回赋给e引用,这样,e指向了堆中创建的这个"aaa"字符串对象。当执行String f=new String("aaa")时, 因为采用new关键字创建对象时,每次new出来的都是一个新的对象,也即是说引用e和f指向的是两个不同的对象,因此语句System.out.println(e == f)输出:false。
分析:因为例子中的a0和a1中的"helloworld”都是字符串常量,它们在编译期就被确定了,所以a0==a1为true;而"hello”和"world”也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以a2也同样在编译期就被解析为一个字符串常量,所以a2也是常量池中"helloworld”的一个引用。所以我们得出a0==a1==a2。