目录
基础知识
在Java中创建String对象有两种方法分别为:
String a = "123";
String b = new String("456");
这两种方法有什么本质的区别呢?先来看看String a = "123";是如何生成的,其主要步骤分为两步:
1、判断常量池有没有“123”对象
2、如果有,a直接指向“123”;如果没有,在常量池创建“123”对象,然后使a指向。
由于之前常量池没有“123”对象,所以就创建一个并使a指向它。
接着,来看看String b = new String("456");是如何创建的,其主要步骤也分为两步:
1、在堆内存中单独开辟出一个内存空间存放“456”对象,使b指向它。
2、如果常量池中没有“456”对象则创建,如果有则不创建。
了解两种不同的创建String类的方法,就可以得到以下两个结论:
1、使用直接赋值,如果对象内容是一样的,则引用地址也是一样的。
2、使用new赋值,即使对象内容是一样的,引用地址也是不一样的。
String a = "123";
String b = "123";
System.out.println(a == b); //true
String c = new String("123");
String d = new String("123");
System.out.println(c == d); //false
String.intern()解析
在JDK1.6及以前,inter()方法是这样描述的:
当字符串在常量池存在时,则返回常量池中的字符串;当字符串在常量池不存在时,则在常量池中拷贝一份,然后再返回常量池中的字符串。
还是拿之前的两种不同方式为例:
由于两种创建对象的方法都会在常量池中有一份字符串,所以直接返回常量池对象。区别在于,返回的引用地址不同。
String a = "123";
String m = a.intern();
System.out.println(a == m); //true
String b = new String("456");
String n = b.intern();
System.out.println(b == n); //false
之前的例子已经在常量池中存在字符串了,重点在于常量池如果没有字符串的例子,看以下的例子:
String a = new String("1")+new String("2");
String b = a.intern();
System.out.println(a == b); //false
一步一步来分析,首先第一行String a = new String("1")+new String("2");中应分分为三步来完成:
1、分别在堆内存和常量池创建“1”字符串
2、分别在堆内存和常量池创建“2”字符串
3、运算加法,在堆内存中创建“12”字符串(注意:由StringBuffer实现,并不会在常量池中创建)
此时,走到第二步a.intern();根据JDK1.6时的流程是在常量池中拷贝一份“12”字符串,然后将字符串返回给b。
可以清除的看到a和b的引用地址并不一样,所以返回false。
此时再来看看JDK1.6以后是如何定义intern()方法的:
当字符串在常量池存在时,则返回常量池中的字符串;当字符串在常量池不存在时,则把堆内存中此对象引用添加到常量池中,然后再返回此引用。
可以看到如果字符串已经在常量池中的话,是和JDK1.6及以前没有任何区别的。所以仍然满足:
String a = "123";
String m = a.intern();
System.out.println(a == m); //true
String b = new String("456");
String n = b.intern();
System.out.println(b == n); //false
但当常量池中没有字符串时情况就发生了变化:
String a = new String("1")+new String("2");
String b = a.intern();
System.out.println(a == b); //true
同样一步一步分析,第一步是一样的:
当执行到 String b = a.intern();是就发生 了变化:
可以看到,JDK1.6及以前是将堆内存的对象拷贝一份到常量池中,JDK1.6以后是将此对象的引用放入常量池中。 所以JDK1.6以后,实际上b指向的是堆内存中的“12”,即和a指向是相同的,所以返回true。
因此,JDK1.6以后的intern()方法可以有效的减少内存的占用,提高运行时的性能。