随笔 - 312, 文章 - 14, 评论 - 1393, 引用 - 0
数据加载中……

关于Java String对象创建问题解惑

本文为原创,如需转载,请注明作者和出处,谢谢!

先看看下面的代码

    
public String makinStrings()
    {
        String s 
= "Fred";
        s 
= s + "47";
        s 
= s.substring(25);
        s 
= s.toUpperCase();
        
return s.toString();
    }


问:调用makinStrings方法会创建几个String对象呢。  答案:3个


    上面的方法有五条语句:现在让我们来一条一条分析一下。

String s = "Fred";   结论:创建了一个String对象

这条语句相当于String s = new String("Fred");
因此,毫无疑问,第一条语句创建了一个String对象,我想没有有疑问吧?

s = s + "47";   结论:未创建String对象

这条语句也许很多人认为是创建了String对象,我一开始也是这么认为的。但是为了验证我的想法。决定
用点法术恢复这条语句的本来面目。(有很多时候,编译器总是在里面搞一些小动作,javac.exe也不例外)

现在找到这个程序所生成的.class文件(假设是Test.class),找一个反编译工具,我推荐JAD,可以http://www.softpedia.com/progDownload/JAD-Download-85911.html下载
下载后,有一个jad.exe,将其路径放到环境变量path中(只限windows)。并在.class文件的当前路径执行如下的命令:

jad Test

然后大喊一声“还我本来面目”

会在当前目录下生成一个Test.jad文件,打开它,文件内容如下:

 
    
public String makinStrings()
    {
        String s 
= "Fred";
        s 
= (new StringBuilder(String.valueOf(s))).append("47").toString();
        s 
= s.substring(25);
        s 
= s.toUpperCase();
        
return s.toString();
    }
 

    哈哈,其他的语句都没变,只有第二条变长了,虽然多了个new,但是建立的是StringBuilder对象。原来
这是java编译器的优化处理。原则是能不建String对象就不建String对象。而是用StringBuilder对象
加这些字符串连接起来,相当于一个字符串队列。这种方式尤其被使用在循环中,大家可以看看下面的代码:
        String s = "";
        for(int i=0; i < 10000000; i++)
            s += "aa";
    没有哪位老大认为这是建立了10000000个String对象吧。但不幸的是,上面的代码虽然没有建立10000000个String对象
但却建立了10000000个StringBuilder对象,那是为什么呢,自已用jad工具分析一下吧。
正确的写法应该是:

        StringBuilder sb = new StringBuilder("");
        for(int i=0; i < 10000000; i++)
            sb.append(String.valueOf(i));

 s = s.substring(2, 5);     结论:创建了一个String对象
 也许有很多人一开始就认为这条语句是创建了一个String对象,那么恭喜你,这条语句确实创建了一个String对象
 实际上就是substring方法创建了一个String对象。这也没什么复杂的,自已下一个JDK源代码,看看substring是如何实现的
 就可以知道了。我先说一下吧。先不用管substring是如何实现的,反正在substring方法返回时使用了一个new显式地建立了一个String对象
 不信自己看看源码。
s = s.toUpperCase();   结论:创建了一个String对象

toUpperCase()和substring方法类似,在返回时也是使用了new建立了一个String对象。

return s.toString();   结论:未创建String对象

toString方法返回的就是this,因此,它的返回值就是s。

这道题还算比较简单,再给大家出一个更复杂一点的,也是关于String对象的创建的(只是改了一个原题)。

    public String makinStrings()
    {
        String s 
= "Fred";
        s 
= s + "Iloveyou.".substring(1).toLowerCase();
        s 
= s.substring(0);
        s 
= s.substring(0,1).toUpperCase();
        
return s.toString();
    }


先公布答案吧,上述代码也创建了3个String对象,哈哈!


为什么呢?

要想知道为什么,先得弄清楚substring、toLowerCase和toUpperCase什么时候创建String对象,什么时候不创建对象。

substring方法在截取的子字符串长度等于原字符串时,直接返回原字符串。并不创建新的String对象。

toLowerCase方法在字符串中更本没有需要转换的大写字母时直接返回原字符串,如"abcd".toLowerCase()直接返回abcd,并不创建新的String对象

toUpperCase方法和toLowerCase类似。"ABCD".toUpperCase()直接返回ABCD。


知道了这个,上面的代码就非常清楚了。

    public String makinStrings()
    {
        String s 
= "Fred";     // 创建一个String对象
        s = s + "Iloveyou.".substring(1).toLowerCase();  // substring(1)创建一个String对象,由于toLowerCase()转换的字符串是"loveyou.",没有大写字母,因此,它不创建新的String对象
        s = s.substring(0);   // 由于substring(0)截获的是s本身,因此,这条语句不创建新的String对象
        s = s.substring(0,1).toUpperCase();  // substring(0,1)创建了一个String对象,但由于substring(0,1)的结果是"F",为一个大写字母,因此,toUpperCase直接返回"F"本身。
        return s.toString();
    }






Android开发完全讲义(第2版)(本书版权已输出到台湾)

http://product.dangdang.com/product.aspx?product_id=22741502



Android高薪之路:Android程序员面试宝典 http://book.360buy.com/10970314.html


新浪微博:http://t.sina.com.cn/androidguy   昵称:李宁_Lining

posted on 2008-04-27 10:01 银河使者 阅读(3161) 评论(18)  编辑  收藏 所属分类: java 原创

评论

# re: 关于Java String对象创建问题解惑  回复  更多评论   

既然考虑编译器因素了,为什么不考虑编译器直接把这段代码constant folding + constant propagation然后直接返回一个最终结果呢?
2008-04-27 10:33 | ZelluX

# re: 关于Java String对象创建问题解惑  回复  更多评论   

好文!!!
2008-04-27 10:33 | tomjamescn

# re: 关于Java String对象创建问题解惑  回复  更多评论   

如果之前有个函数,里面已经执行了过
String s = "Fred";
那么然后你再调用 makinStrings() 的时候,第一步应该不再创建一个 "Fred"了吧?
2008-04-27 10:46 | stanleyxu

# re: 关于Java String对象创建问题解惑  回复  更多评论   

你是自己研究的吧,基本上差不多,只是
String s = "Fred";
这句不会在运行时创建String对象。
s = (new StringBuilder(String.valueOf(s))).append("47").toString();
这句StringBuilder.toString();是会创建String对象的。

这样一加一减,你的答案还是可用的。
2008-04-27 11:01 | Matthew Chen

# re: 关于Java String对象创建问题解惑  回复  更多评论   

to Matthew Chen

String s = "Fred"; 这句我查了一下bytecode,应该是一个压栈的操作。如果要是创建String对象,就是四个String对象了。

StringBuilder类的toString确实创建了一个String对象,下面是toString方法的代码。

public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}

2008-04-27 11:15 | 银河使者

# re: 关于Java String对象创建问题解惑  回复  更多评论   

java应该和delphi和vc一样,使用了引用计数原理。
所以以此类推,给字符串赋值常量的时候,该常量是编译时由编译器(在堆上)创建。
所以String s = "Fred";严格的说,第一次使用的时候是为它创建了一个实例。
其他的部分你自己也已经说明了:
如果小写字符串执行toLowerCase()不会再(在栈上)创建实例,而是对它的引用计数加一。
如果执行substring(0),同理。
赋值的话,难说!如果有另外一个string b=s;然后你再执行s+='new..';可能就是会再创建一个实例,而不是不变了。(至少delphi里面是这样的)

麻烦lz验证一下我的话,我只是从语义出发,做的一些推断。
2008-04-27 11:34 | stanleyxu

# re: 关于Java String对象创建问题解惑  回复  更多评论   

没有必要太在意这个问题.
因为不同的jdk下,比如sun jdk,ibm jdk等,答案是非唯一的.而且不同的jdk版本下,也有可能存在不同的答案.
sun jdk是对String优化做的最多的.所以才这么拗.很多公司都拿string做面试题,没有意义的.
至于s=s+"47",至少在jdk5以下,在String pool中肯定会创建一个新的对象.但是之后的版本怎么处理,就没去看过了.
2008-04-27 12:04 | stone2083

# re: 关于Java String对象创建问题解惑  回复  更多评论   

补充一点:lz未说明从计算每步是否创建string实例还是问从函数返回时,内存中还有多少实例?这个结果是不一样的。根据引用计数的原理,当一个字符串的refcount为0之后,会被释放。而且根据编译器的不同,可能会采取不同的策略。如果字符串变长,应该会去开辟一块新的内存,而如果字符串变短,可能只是在原有的内存上做少许处理。
2008-04-27 12:09 | stanleyxu

# re: 关于Java String对象创建问题解惑  回复  更多评论   

再补充一点:楼上的stone2083提醒了我,字符串变长的时候,应该要创建一个新的变量。我不太清楚string在java中如何分配的,一般应该是array形式,即申请一块连续的内存。这样的话,如果要加长字符串,应该要重新申请一块新的连续内存了。因为编译器无法保证当前字符串所占内存的后续连续地址是否是可用的。
2008-04-27 12:12 | stanleyxu

# re: 关于Java String对象创建问题解惑[未登录]  回复  更多评论   

to 银河使者
看不懂你的意思,是认同我的说法吗?因为看你的回复和我的意思基本一致。

to stanleyxu
你提到了编译器,我想那时生成的保存在静态对象池中的string是字节码的一部分,运行时调入内存的,而在内存中直接创建的string对象确实和普通的数组一样的方式,没有为连接做特别的结构考虑。
2008-04-27 22:05 | Matthew Chen

# re: 关于Java String对象创建问题解惑  回复  更多评论   

据说这是一道SCJP的考试题,哈哈,哪天再弄点SCJP的试题分析一下。再来几次头脑风暴!
2008-04-27 22:21 | 银河使者

# re: 关于Java String对象创建问题解惑[未登录]  回复  更多评论   

String s = "Fred"; 结论:创建了一个String对象

这条语句相当于String s = new String("Fred");
因此,毫无疑问,第一条语句创建了一个String对象,我想没有有疑问吧?

最近在听浪曦网风中叶老师的面试视频,听他讲 String s = new String("Fred");这句话应该是创建了2个对象,那你说String s = "Fred";这条语句相当于String s = new String("Fred");行吗?到底谁是对的?
2008-04-28 17:26 | 在威尼斯流浪

# re: 关于Java String对象创建问题解惑  回复  更多评论   

String s = new String("Fred")这句,new 肯定是创建了一个String对象。但是"Fred",我看了一下bytecode,好象是直接压栈了,并不创建String对象。
2008-04-28 19:29 | 银河使者

# re: 关于Java String对象创建问题解惑  回复  更多评论   

下面是String s = new String("Fred")的bytecode,看看吧,只有一个new

public class Test
{
public Test()
{
// 0 0:aload_0
// 1 1:invokespecial #1 <Method void Object()>
// 2 4:return
}

public void main(String args[])
{
String s = new String("Fred");
// 0 0:new #2 <Class String>
// 1 3:dup
// 2 4:ldc1 #3 <String "Fred">
// 3 6:invokespecial #4 <Method void String(String)>
// 4 9:astore_2
// 5 10:return
}
}

还有就是在本文中我的分析有一点小毛病,就是String s = "Fred"的确在运行时不创建String对象,只是压栈操作,另外说String s = "Fred"相当于String s = new String("Fred")也不严谨。从底层上它们还是有区别的。但可以肯定的是String s = new String("Fred")在当前方法中肯定是建立一个String对象,而String s = "Fred"并不创建对象。而StringBuilder的toString创建了一个String对象。这从上面的bytecode就可以看出。

四楼的Matthew Chen说的没错。多谢提醒。
2008-04-28 19:42 | 银河使者

# re: 关于Java String对象创建问题解惑  回复  更多评论   

从评论看出lz也没真正明白字符串分配究竟在干什么。

String s1 = "Fred";
String s2 = "Fred";
// s1 == s2

String s1 = new String("Fred");
String s2 = "Fred";
// s1 != s2

String s1 = new String("Fred");
String s2 = new String("Fred");
// s1 != s2

看看我之前的评论然后自己慢慢吧。
2008-04-29 12:41 | stanleyxu

# re: 关于Java String对象创建问题解惑  回复  更多评论   

to tanleyxu

从bytecode上看是String s = "abc" 不建立String对象。其实也没必要这么较真。至于jvm内部到底如何工作,只有看jvm的源代码了。jvm、MSIL这些东西对String对处理都不太一样,无法只从语意上分析。不知道tanleyxu最后这个评论:

String s1 = "Fred";
String s2 = "Fred";
// s1 == s2

String s1 = new String("Fred");
String s2 = "Fred";
// s1 != s2

String s1 = new String("Fred");
String s2 = new String("Fred");
// s1 != s2

是什么意思.如果String s = "Fred", s是压栈操作,用的是方法栈中的空间。new String(),用的是堆的空间,这没错。难道我最后补充的有问题。我可不这么认为。实际上String s = "aa"确实不创建新的String。反正在bytecode里没找到new。



2008-04-29 13:15 | 银河使者

# re: 关于Java String对象创建问题解惑  回复  更多评论   

傻逼文章
2008-06-06 10:04 | w

# re: 关于Java String对象创建问题解惑  回复  更多评论   

哈哈,还是得看评论啊。热闹。
2015-05-28 17:30 | 莾s

只有注册用户登录后才能发表评论。


网站导航: