Topic: JAVA面试题解惑系列(二)——到底创建了几个String对象?

  Print this page

1.JAVA面试题解惑系列(二)——到底创建了几个String对象? Copy to clipboard
Posted by: 臧圩人
Posted on: 2008-06-30 18:19

原文地址:http://zangweiren.javaeye.com/blog/209895

作者:臧圩人(zangweiren)
网址:http://zangweiren.javaeye.com

>>>转载请注明出处!<<<

我们首先来看一段代码:
Java代码
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的构造器:
Java代码
public String(String original) {
//other code ...
}

大家都知道,我们常用的创建一个类的实例(对象)的方法有以下两种:

使用new创建对象。
调用Class类的newInstance方法,利用反射机制创建对象。

我们正是使用new调用了String类的上面那个构造器方法创建了一个对象,并将它的引用赋值给了str变量。同时我们注意到,被调用的构造器方法接受的参数也是一个String对象,这个对象正是"abc"。由此我们又要引入另外一种创建String对象的方式的讨论——引号内包含文本。

这种方式是String特有的,并且它与new的方式存在很大区别。
Java代码
String str="abc";

毫无疑问,这行代码创建了一个String对象。
Java代码
String a="abc";
String b="abc";

那这里呢?答案还是一个。
Java代码
String a="ab"+"cd";

再看看这里呢?答案是三个。有点奇怪吗?说到这里,我们就需要引入对字符串池相关知识的回顾了。

在JAVA虚拟机(JVM)中存在着一个字符串池,其中保存着很多String对象,并且可以被共享使用,因此它提高了效率。由于String类是final的,它的值一经创建就不可改变,因此我们不用担心String对象共享而带来程序的混乱。字符串池由String类维护,我们可以调用intern()方法来访问字符串池。

我们再回头看看String a="abc";,这行代码被执行的时候,JAVA虚拟机首先在字符串池中查找是否已经存在了值为"abc"的这么一个对象,它的判断依据是String类equals(Object obj)方法的返回值。如果有,则不再创建新的对象,直接返回已存在对象的引用;如果没有,则先创建这个对象,然后把它加入到字符串池中,再将它的引用返回。因此,我们不难理解前面三个例子中头两个例子为什么是这个答案了。

对于第三个例子:
Java代码
String a="ab"+"cd";

"ab"和"cd"分别创建了一个对象,它们经过“+”连接后又创建了一个对象"abcd",因此一共三个,并且它们都被保存在字符串池里了。

现在问题又来了,是不是所有经过“+”连接后得到的字符串都会被添加到字符串池中呢?我们都知道“==”可以用来比较两个变量,它有以下两种情况:

如果比较的是两个基本类型(char,byte,short,int,long,float,double,boolean),则是判断它们的值是否相等。
如果表较的是两个对象变量,则是判断它们的引用是否指向同一个对象。

下面我们就用“==”来做几个测试。为了便于说明,我们把指向字符串池中已经存在的对象也视为该对象被加入了字符串池:
Java代码
public class StringTest {
public static void main(String[] args) {
String a = "ab";// 创建了一个对象,并加入字符串池中
System.out.println("String a = "ab";");
String b = "cd";// 创建了一个对象,并加入字符串池中
System.out.println("String b = "cd";");
String c = "abcd";// 创建了一个对象,并加入字符串池中

String d = "ab" + "cd";
// 如果d和c指向了同一个对象,则说明d也被加入了字符串池
if (d == c) {
System.out.println(""ab"+"cd" 创建的对象 "加入了" 字符串池中");
}
// 如果d和c没有指向了同一个对象,则说明d没有被加入字符串池
else {
System.out.println(""ab"+"cd" 创建的对象 "没加入" 字符串池中");
}

String e = a + "cd";
// 如果e和c指向了同一个对象,则说明e也被加入了字符串池
if (e == c) {
System.out.println(" a +"cd" 创建的对象 "加入了" 字符串池中");
}
// 如果e和c没有指向了同一个对象,则说明e没有被加入字符串池
else {
System.out.println(" a +"cd" 创建的对象 "没加入" 字符串池中");
}

String f = "ab" + b;
// 如果f和c指向了同一个对象,则说明f也被加入了字符串池
if (f == c) {
System.out.println(""ab"+ b 创建的对象 "加入了" 字符串池中");
}
// 如果f和c没有指向了同一个对象,则说明f没有被加入字符串池
else {
System.out.println(""ab"+ b 创建的对象 "没加入" 字符串池中");
}

String g = a + b;
// 如果g和c指向了同一个对象,则说明g也被加入了字符串池
if (g == c) {
System.out.println(" a + b 创建的对象 "加入了" 字符串池中");
}
// 如果g和c没有指向了同一个对象,则说明g没有被加入字符串池
else {
System.out.println(" a + b 创建的对象 "没加入" 字符串池中");
}
}
}

运行结果如下:

String a = "ab";
String b = "cd";
"ab"+"cd" 创建的对象 "加入了" 字符串池中
a +"cd" 创建的对象 "没加入" 字符串池中
"ab"+ b 创建的对象 "没加入" 字符串池中
a + b 创建的对象 "没加入" 字符串池中

从上面的结果中我们不难看出,只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中,对此我们不再赘述。因此我们提倡大家用引号包含文本的方式来创建String对象以提高效率,实际上这也是我们在编程中常采用的。

接下来我们再来看看intern()方法,它的定义如下:
Java代码
public native String intern();

这是一个本地方法。在调用这个方法时,JAVA虚拟机首先检查字符串池中是否已经存在与该对象值相等对象存在,如果有则返回字符串池中对象的引用;如果没有,则先在字符串池中创建一个相同值的String对象,然后再将它的引用返回。

我们来看这段代码:
Java代码
public class StringInternTest {
public static void main(String[] args) {
// 使用char数组来初始化a,避免在a被创建之前字符串池中已经存在了值为"abcd"的对象
String a = new String(new char[] { 'a', 'b', 'c', 'd' });
String b = a.intern();
if (b == a) {
System.out.println("b被加入了字符串池中,没有新建对象");
} else {
System.out.println("b没被加入字符串池中,新建了对象");
}
}
}

运行结果:

b没被加入字符串池中,新建了对象

如果String类的intern()方法在没有找到相同值的对象时,是把当前对象加入字符串池中,然后返回它的引用的话,那么b和a指向的就是同一个对象;否则b指向的对象就是JAVA虚拟机在字符串池中新建的,只是它的值与a相同罢了。上面这段代码的运行结果恰恰印证了这一点。

最后我们再来说说String对象在JAVA虚拟机(JVM)中的存储,以及字符串池与堆(heap)和栈(stack)的关系。我们首先回顾一下堆和栈的区别:

栈(stack):主要保存基本类型(或者叫内置类型)(char、byte、short、int、long、float、double、boolean)和对象的引用,数据可以共享,速度仅次于寄存器(register),快于堆。
堆(heap):用于存储对象。

我们查看String类的源码就会发现,它有一个value属性,保存着String对象的值,类型是char[],这也正说明了字符串就是字符的序列。

当执行String a="abc";时,JAVA虚拟机会在栈中创建三个char型的值'a'、'b'和'c',然后在堆中创建一个String对象,它的值(value)是刚才在栈中创建的三个char型值组成的数组{'a','b','c'},最后这个新创建的String对象会被添加到字符串池中。如果我们接着执行String b=new String("abc");代码,由于"abc"已经被创建并保存于字符串池中,因此JAVA虚拟机只会在堆中新创建一个String对象,但是它的值(value)是共享前一行代码执行时在栈中创建的三个char型值值'a'、'b'和'c'。

说到这里,我们对于篇首提出的String str=new String("abc")为什么是创建了两个对象这个问题就已经相当明了了。

2.Re:JAVA面试题解惑系列(二)——到底创建了几个String对象? [Re: 臧圩人] Copy to clipboard
Posted by: Joffeec
Posted on: 2008-07-01 07:31

臧圩人 wrote:
…………
System.out.println("String a = "ab";");
…………
System.out.println("String b = "cd";");

…………
System.out.println(""ab"+"cd" 创建的对象 "加入了" 字符串池中");
…………

楼主解释得很详细……有适当的用到例子使人更容易理解……
嗯,但是像这些

…………
System.out.println("String a = "ab";");
……

在编译的时候都会提示错误

类型PrintStream中的方法println(String)对于参数(String,String)不适用

出现问题的原因:
在一个字符串中没有用一个转义序列来指定一个双引号,则在编译时会把双引号解释为一个字符的结束。这也就是为什么在错误提示中说方法println(String)对于参数(String,String)不适用的原因。

解决问题的方法:

在字符串中用一个转义序列来指定一个双引号


System.out.println("String a = \"ab\";");
…………
System.out.println("String b = \"cd\";");

…………
System.out.println("\"ab\"+\"cd\" 创建的对象 \"加入了\" 字符串池中");
…………

3.Re:JAVA面试题解惑系列(二)——到底创建了几个String对象? [Re: 臧圩人] Copy to clipboard
Posted by: 臧圩人
Posted on: 2008-07-01 10:40

谢谢楼上的提醒,我原帖里是有转意字符的,只是拷贝过来后被编辑器自己过滤掉了

4.Re:JAVA面试题解惑系列(二)——到底创建了几个String对象? [Re: 臧圩人] Copy to clipboard
Posted by: JiafanZhou
Posted on: 2008-07-02 17:02

I found this article is not very straightforward to follow for some beginners in Java.
e.g.
- "紧接着这段代码之后的往往是这个问题,那就是这行代码究竟创建了几个String对象呢?相信大家对这道题并不陌生,答案也是众所周知的,2个。"
I have not a clue why it creates two objects until I finish the whole document.

- "intern()" method is a very important feature in Java and we have not discussed it in good details here.

But as a whole, it is a good article to read about. 3 points.

5.Re:JAVA面试题解惑系列(二)——到底创建了几个String对象? [Re: 臧圩人] Copy to clipboard
Posted by: 臧圩人
Posted on: 2008-07-02 22:33

谢谢HenryShanley版主的建议!

关于intern()方法,我手头也没有更详细的资料,所以没有更深入的去写。

6.Re:JAVA面试题解惑系列(二)——到底创建了几个String对象? [Re: 臧圩人] Copy to clipboard
Posted by: andy_wang_5
Posted on: 2008-07-03 08:58

楼主讲的很精辟.
我认为 String str=new String("abc"); 在堆里肯定创建新对象了,至于栈里要看具体情况而定了,如果栈里已经有"abc"的内存空间.就不用创建了.
我这中理解和楼主讲的也不矛盾.


  public static void main(String[] args) {
    String str1 = "abc";
    String str2 = new String("abc");
    System.out.println(str1 == str2); // false
    System.out.println(str1 == str2.intern()); // true
    System.out.println(str1 == str1.intern()); // true
    System.out.println(str2 == str2.intern()); // false
  }



可以看出intern()返回的是栈的对象. 就看我们怎么去用它了

7.Re:JAVA面试题解惑系列(二)——到底创建了几个String对象? [Re: 臧圩人] Copy to clipboard
Posted by: 臧圩人
Posted on: 2008-07-03 12:35

栈里存储的是'a'、'b'、'c'三个char型的字符。
"abc"对象保存在堆中。

8.Re:JAVA面试题解惑系列(二)——到底创建了几个String对象? [Re: 臧圩人] Copy to clipboard
Posted by: JiafanZhou
Posted on: 2008-07-03 17:09

ok, I think this topic is really really *complicated* by us, and will really really confuse others.

To all the Java beginners, if you want to use Strings in Java, you can use any of the ways provided above, disregarding how many Object instances have been created whether in the stack or in the heap or the pool of string. (i.e. It is the JVM to handle all these magic things behind scene, we don't have to care about these at all, unless there is a specific reason you want to optimize and profile your application -- my 2 cent)

So as a general rule if you don't understand this issue, forget about this article and just go ahead and use the magic Java String.

-----------------------------------------------------------------------------------------------------------
To all the Java language syntax experts who want to know more about this issue like "臧圩人" and "andy_wang_5". After I read about the Javadoc of the String.intern(), I found something interesting:

intern() method is a native method and it will return a string that has the same contents as this string, but is guaranteed to be from a pool of unique strings. In other words, it returns the object reference from the *pool of string* in the heap.
(A pool of strings, initially empty, is maintained privately by the String object.)

Hence, talking about how the Object instances are allocated in the memory, they are outlined as follows:

- new String()
creates an object instance in the heap and returns an object reference to the instance in the heap.
- "xyz" (create string)
creates an object instance in the "pool of string" (pool of string is also objects so created in the heap) and returns an object reference to the instance in the "pool of string". (Assuming no same strings created in the pool of string)
- intern() method
returns the object reference in the pool of string which has the same string content created in the heap. Otherwise this string object is added to the pool and a reference to this String object is returned. (NB. different object reference from the one in the memory heap I have all overlooked it)

9.Re:JAVA面试题解惑系列(二)——到底创建了几个String对象? [Re: 臧圩人] Copy to clipboard
Posted by: 臧圩人
Posted on: 2008-07-03 22:14

谢谢HenryShanley的解答。
我就是把字符串池看作类似ArrayList之类的对象,它本身作为对象在heap中创建,同时它又是一个对象容器,可以存放很多对象进去,实质是它保存了这些对象的引用,这些被包含的对象在heap中仍只存在一份。


   Powered by Jute Powerful Forum® Version Jute 1.5.6 Ent
Copyright © 2002-2021 Cjsdn Team. All Righits Reserved. 闽ICP备05005120号-1
客服电话 18559299278    客服信箱 714923@qq.com    客服QQ 714923