全部博文(695)
分类: Java
2016-04-25 19:15:01
String str=new String("abc"); 紧接着这段代码之后的往往是这个问题,那就是这行代码究竟创建了几个String对象呢?
相信大家对这道题并不陌生,答案也是众所周知的,2个。
接下来我们就从这道题展开,一起回顾一下与创建String对象相关的一些JAVA知识。
我们可以把上面这行代码分成String str、=、"abc"和new String()四部分来看待。String str只是定义了一个名为str的String类型的变量,因此它并没有创建对象;=是对变量str进行初始化,将某个对象的引用(或者叫句柄)赋值给 它,显然也没有创建对象;现在只剩下new String("abc")了。那么,new String("abc")为什么又能被看成"abc"和new String()呢?
我们来看一下被我们调用了的String的构造器:
public String(String original) { //other code ... } 大家都知道,我们常用的创建一个类的实例(对象)的方法有以下两种:
一、使用new创建对象。
二、调用Class类的newInstance方法,利用反射机制创建对象。
我们正是使用new调用了String类的上 面那个构造器方法创建了一个对象,并将它的引用赋值给了str变量。同时我们注意到,被调用的构造器方法接受的参数也是一个String对象,这个对象正 是"abc"。由此我们又要引入另外一种创建String对象的方式的讨论——引号内包含文本。
这种方式是String特有的,并且它与new的方式存在很大区别。
String str="abc";
毫无疑问,这行代码创建了一个String对象。
String a="abc"; String b="abc"; 那这里呢?
答案还是一个。
String a="ab"+"cd"; 再看看这里呢?
答案是三个。说到这里,我们就需要引入对字符串池相关知识的回顾了。
在JAVA虚拟机(JVM)中存在着一个字符 串池,其中保存着很多String对象,并且可以被共享使用,因此它提高了效率。由于String类是final的,它的值一经创建就不可改变,因此我们 不用担心String对象共享而带来程序的混乱。字符串池由String类维护,我们可以调用intern()方法来访问字符串池。
我们再回头看看String a="abc";,这行代码被执行的时候,JAVA虚拟机首先在字符串池中查找是否已经存在了值为"abc"的这么一个对象,它的判断依据是String 类equals(Object obj)方法的返回值。如果有,则不再创建新的对象,直接返回已存在对象的引用;如果没有,则先创建这个对象,然后把它加入到字符串池中,再将它的引用返 回。因此,我们不难理解前面三个例子中头两个例子为什么是这个答案了。
只有使用引号包含文本的方式创建的 String对象之间使用“+”连接产生的新对象才会被加入字符串池中。对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的 新对象都不会被加入字符串池中,对此我们不再赘述。因此我们提倡大家用引号包含文本的方式来创建String对象以提高效率,实际上这也是我们在编程中常 采用的。
栈(stack):主要保存基本类型(或者叫内置类型)(char、byte、short、int、long、float、double、boolean)和对象的引用,数据可以共享,速度仅次于寄存器(register),快于堆。
堆(heap):用于存储对象
所以对于语句String s = new String("XYZ") + new String("XYZ");
近在论坛上看到关于String s = new String("XYZ") + new String("XYZ");到底创建几个对象的讨论,觉得比较有意思,在此总结一下。
在JAVA中除了8种基本类型之外,其他的都是类对象及其引用。所以 "XYZ"在JAVA中是一个String对象,对于String类对象来说它的对象值是不能修改的,也就是具有不变性。
但是在下面一段程序中:
打印出s的结果是"Java",看起来s所引用的String变量好像是被修改了,但是如果你了解JVM(Java虚拟机)处理String变量时的机制,你就会知道事实并非如此。
在JVM的工作过程中,会创建一片的内存空间专门存入String对象,我们把这片内存空间叫做String池。
对于语句String s= "Hello";,当JVM看到"Hello"时,会在String池创建String对象存储它,并将它的引用返回给String变量s。
语句s = "Java";,当JVM看到"Java"时,会在String池创建新的String对象存储它,再把新建的String对象的引用返回给String变量s。而原先的String对象"Hello"仍然在String池内,并没有消失,它是不能被修改的。
所以我们仅仅是改变了变量s的引用,而没有改变它所引用的对象,因为String对象的值是不能被修改的。
String s1 =
"Java";,JVM首先在String池里面看能否找到字符串"Java",如果找到则返回它的引用给s1,否则创建新的String对象,放到
String池里。这里由于有s = "Java",所以对象已经被引用,所以依据规则s和s1都是引用同一个对象。所以s==s1返回true。(注:
比较运算符==,对于非基本类型,是比较两引用是否引用内存中的同一个对象)。
String s2 = new String( "Java");,JVM首先还是在String池里面看能否找到字符串
"Java",如果找到,不做任何事情;否则创建新的String对象,放到String池里面。由于遇到了new关键字,还会在内存上(不是
String池里面)创建String对象存储
"Java",并将内存上的(不是String池里面的)String对象返回给s2。所以s==s2将返回false,因为它们引用的不是同一个对象。
所以对于语句String s = new String("XYZ") + new String("XYZ");
JVM先在String池中创建一个String对象存储"XYZ",然后由于遇到new关键字,再在内存上创建一个String对象存储"XYZ";
接着由于String池中已经有了"XYZ"的对象,所以第二个new语句不会在String池中创建对象,而只会在内存上创建一个String对象;
最后两个字符串相加会在String池中创建一个String对象"XYZXYZ",并将其引用传给s。(因为最后一个相加只是+没有new所以只在String池中有,内存堆上不会有)
所以总共会创建4个String对象。
3、String、StringBuffer、StringBuilder的联系与区别
上面已经分析了String的本质了,下面简单说说StringBuffer和StringBuilder。 StringBuffer和StringBuilder都继承了抽象类AbstractStringBuilder,这个抽象类和String一样也定义了char[] value和int count,但是与String类不同的是,它们没有final修饰符。因此得出结论:String、
StringBuffer和StringBuilder在本质上都是字符数组,不同的是,在进行连接操作时,String每次返回一个新的String实
例,而StringBuffer和StringBuilder的append方法直接返回this,所以这就是为什么在进行大量字符串连接运算时,不推荐
使用String,而推荐StringBuffer和StringBuilder。那么,哪种情况使用StringBuffe?哪种情况使用StringBuilder呢?
关于StringBuffer和StringBuilder的区别,翻开它们的源码,下面贴出append()方法的实现。
上面第一张图是StringBuffer中
append()方法的实现,第二张图为StringBuilder对append()的实现。区别应该一目了然,StringBuffer在方法前加了
一个synchronized修饰,起到同步的作用,可以在多线程环境使用。为此付出的代价就是降低了执行效率。因此,如果在多线程环境可以使用StringBuffer进行字符串连接操作,单线程环境使用StringBuilder,它的效率更高。
转自:
http://my.oschina.net/xiaohui249/blog/170013
http://www.cnblogs.com/ydpvictor/archive/2012/09/09/2677260.html
http://blog.csdn.net/wuxianglong/article/details/6119399