我们经常能看到一些问题,比如String字符串创建了几个对象,判断是否相等,等等。在这里做一个总结。
String字符串在内存中的存储
我们常把java内存区分为“堆”和“栈”,但实际上其内存结构用下图来描述更准确:
其中方法区存储虚拟机加载的类信息,常量,静态变量,编译后的代码等数据。运行时常量池是方法区的一部分,我们接下来要说的字符串就保存在常量池中。在java中,所有的字符串都是常量,即每一个不同的字符串都会在常量池中创建一条数据。
示例
简单了解了上述知识,我们来看如下几个例子:
String s=new String(“a”)创建了几个对象?
两个对象,一个是“a”对象,在常量池中创建;一个是new关键字在堆中创建的“a”的拷贝对象,s指向拷贝对象。String s = “a” + “b” + “c”创建了几个对象?
一个,因为”a”“b”“c”都是常量,在编译的时候直接存储其字面值,相当于“abc”。判断下列字符串是否相等
String str1 = "a"; String str2 = "b"; String str3 = "ab"; String str4 = "a" + "b"; String str5 = str1 + str2; String str6 = new String("ab"); System.out.println(str3.equals(str6)); System.out.println(str3 == str6); System.out.println(str3 == str4); System.out.println(str4 == str5); System.out.println((str1+str2) == str5);
返回的结果是:
true
false
true
false
false
前两个很好理解;第三个返回true是的原因见上题;
第四个为什么返回false,str4的值在编译期可以确定,所以直接指向“ab”。字符串相加的时候,如果其中含有变量,则在编译期无法确定其值,所以不会指向“ab”。我们知道“==”是判断地址是否相同,所以结果是false。如果我们将str1与str2声明为final类型,则结果是true。
第五个返回false,是因为虽然同为变量赋值,但是相当于创建了两个不同的对象。关于String中的intern方法
public String intern()
返回字符串对象的规范化表示形式。
一个初始时为空的字符串池,它由类 String 私有地维护。
当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。
它遵循对于任何两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。
所有字面值字符串和字符串赋值常量表达式都是内部的。返回:
一个字符串,内容与此字符串相同,但它保证来自字符串池中。
String str = "java"; String str1 = new StringBuilder("中国").append("钓鱼岛").toString(); String str2 = new StringBuilder("ja").append("va").toString(); System.out.println(str1.intern() == str1); System.out.println(str2.intern() == str2);
返回的结果在JDK1.6中是两个false,在JDK1.7中是true和false。为什么不一样呢?
在JDK1.6中,intern()方法会把首次遇到的字符串实例复制到常量池中,返回的也是常量池中这个字符串实例的引用,所以str1的引用于str1.intern()的引用不同。在JDK1.7中,intern()的实现不会再复制实例,只是在常量池中记录首次出现的实例引用,而其实际对象保存在堆中,所以str1的引用与str1.intern()的引用相同。str2返回false是因为“java”这个引用在之前出现过,不符合“首次原则”。