今天学到面向对象。如创建对象Person p = new Person();
是在堆内存中开辟一个空间存放对象,栈内存中的p指向堆内存中new Person()的首地址。
突然想到,String也是个类,是引用数据类型。
那么问题来了,String s = "abc";是在堆内存中开辟一个空间,并把地址值传递给s,s指向堆内存吗?
问了指导老师,回答说java有一个常量池的存在,目前先不需要了解。
不了解怎么行!开始打一堆代码测试
1)
System.out.println(new Object());//结果:java.lang.Object@3e2de41d
System.out.println(new String());//结果:什么都没输出
为啥new String()不是打印地址值?记得打印对象,是默认调用上帝类的toString()方法。
查看文档,发现String类重写了toString()方法,
继续查看源码,Object类的toString()方法为:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
而String类重写的方法是:
public String toString() {
return this;
}
返回调用者的字符串类型?
因为创建对象时是空参,输出的是空。
继续测试
2)
System.out.println(new String("abc"));//结果:abc
那么大胆假设,String类的实例,就是个常量并存在常量池中?
继续测试
3)
String string常量 = "abc";
String string实例 = new String("abc");
System.out.println(string常量 == string实例);//结果是false
System.out.println(string常量.equals(string实例));//结果是true
测试结果表明假设错误。
只能说明两者值是相同的,但String的实例不是常量。
也就是说,new String()依然是在堆中开辟内存空间。
继续假设,既然值是存在常量池中,new String()指向常量池就行啦!
那么常量池在哪里?不懂问度娘吧
得出总结:常量池存在于.class文件中,在运行期间被JVM装载,并且可以用String 的intern()方法扩充。
1)当使用javac命令编译语句String str = "abc";时,虚拟机已知"abc"是个常量,首先查看常量池中是否有字符串"abc",有就赋值给str,
没有就在常量池中创建一个字符串"abc"后赋值给str。这个在编译时期就能确定。
2)当使用javac命令编译语句String str = new String("abc");时,用new创建的字符串不是常量,不能在编译时期就确定,
所以不放入常量池中,而是赋予它地址空间。
在使用java命令运行期间,JVM装载了常量池,new String()在堆中产生的实例(旧)自行调用String类的intern()方法,查找常量池中是否
有相同编码的字符串常量,找得到就算了,找不到就在常量池开辟空间保存这个字符串。
然后不管在常量池找不找得到,都会在堆内存重新开辟一个空间产生一个新的实例,即实例(新),存放常量池中的字符串。栈中的str就指向
这个新实例。而一开始创建的实例(旧)就变成垃圾等待回收了。
3)同理,八种基本数据类型的封装类大部分都实现了常量池技术,这些类是Byte,Short,Integer,Long,Character,Boolean。
另外两种浮点型的包装类没有实现,这里就不多说啦。
4)既然String类intern()方法这么厉害,它是怎么实现的呢?
查看文档如下:
public String intern()返回字符串对象的规范化表示形式。
一个初始为空的字符串池,它由类 String 私有地维护。
当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串。
否则,将此 String 对象添加到池中,并返回此 String 对象的引用。
查看源码如下:
public native String intern();
妹的这是个本地方法看不了,剧终!
|
|