需要掌握的前提知识
1、知道什么是栈,什么是堆,什么是常量池,以及三者之间的区别。
2、想要不再纠结String的比较问题,需要掌握String literal.以及java虚拟机对String的处理机制
3、明白那些代码是在编译期产生,那些代码在运行期产生,也是有助于我们理解String的比较问题
一、String的声明与初始化的两种主要方式:
1.1、使用关键字new
对于String s1 = new String(“hello”)语句与String s2 = new String(“hello”)语句,存在两个引用对象s1、s2,而且还是两个内容相同字符串对象”hello”,但是纵使这样,他们在内存中的地址也是不同。因为在java中只要new总会产生新的对象。
1.2、直接定义赋值
对于String s1=”hello”;语句与String s2=”hello”语句:在jvm中存在着一个字符串池,其中保存着很多String对象,并且可以被共享使用,s1,s2引用的同一个常量池中的对象。由于String采用了Flyweight的设计模式,当创建一个字符串常量时,例如String s3=”hello”;,会首先在字符串常量池中查找是否已经拥有相同的字符串被定义,其判断依据是String类的equals(Object)的返回值。若已经定义了,则直接获取对其定义的引用,此时不需要创建新的对象。若没有定义,就会在字符串常量池中创建新的对象,再将它的引用返回。由于String属于不可变类,一旦创建成功就不会被修改,因此String对象可以被共享并且不会不出现混乱。
具体而言:
String s="abc";//把"abc"放在常量池中,在编译期产生
String s= new String("abc");//运行期把"abc"放在堆里面
举例如下:
String s1 = "abc";//把字符串"abc"放在常量池中
String s2 = "abc";//s2 引用 常量池中的"abc"对象,因此不会在创建新的对象
String s3= new String("abc");//运行期在堆中创建新的对象
String s4= new String("abc");//运行期在堆中创建新的对象
图1:两种字符串存储方式如下:
二、String字符串的”+”连接字符串
让我们先看下面的代码
String s1="abc";
String s2="abc";
String s3=s1+s2;
String s4="abc"+"abc";
System.out.println(s3==s4);
输出结果为:false;
问什么会是false?在刚学习java没多久时笔者对这个问题也是弄得昏头转向,不明所以。
后来带着困惑看了一些书仔细琢磨后发现两句话就直接解决了:
1) 通过常量表达式计算的String,计算在编译时进行,并将它作为String字面常量对待。
2) 通过连接操作得到的String(非常量表达式),连接操作是运行时进行的,会新创建对象,所以它们是不同的。
当然你如果你已经是java大牛(比如通读java虚拟机和java编程思想)这些当然对你非常简单了。
三、String的”+”号创建对象问题
这个问题需要我们知道那些代码在编译期完成那些代码在运行期完成。
3.1、String str =”a”+”b”+”c”+”d”; 创建了几个对象?
答案:1个。
原因:”a”、”b”、”c”、”d”都是常量,”a”+”b”+”c”+”d”是一个常量表达式,常量表达式得到的String是在编译期计算得到的,因此执行这句话时只有”abcd”一个对象被创建。str代表”abcd”在常量池中的引用.
另外利用final修饰的常量也是同样的效果。
3.2、new String(“abc”)创建了几个对象?
答案:1个或者2个。
原因:如果常量池中已经存在”abc”,该句程序只会在堆中创建一个对象指向常量池的abc。如果常量池中没有”abc”,那么会先在常量池中创建一个”abc”,然后创建一个对象指向常量池的abc。因此答案是一个或者两个。