2011-09-10 54 views
6

我有以下的Java代码:多少String对象将被创建

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

的问题是有点简单:有多少String对象调用此方法时会产生的呢?

一开始,我回答说,5个String对象被创建,但我书中的回答说,仅创建3个对象,并没有给出解释(这是一个SCJP问题)。

从我的角度来看,有5个对象: “Fred”,“47”,“Fred47”,“ed4”,“ED4”。

我还发现一个SCJP考试模拟这个问题,用同样的答案3.

+3

我想象中的编译器将内联前两个语句和删除最后一个(冗余)方法调用。这给你留下了“Fred47”,“ed4”,“ED4”。 –

+1

@Jared's'不是*编译时常量表达式*,因此不会发生。 (编译代码并使用'javap -c',甚至'strings'。) –

+0

可能重复的[Java - 多少个字符串对象?](http://stackoverflow.com/questions/17898236/java-how-many -string-objects) –

回答

15

“弗雷德”和“47”会从字符串文字池。因此,他们不会当方法被调用时被创建。相反,当类加载时(或者更早,如果其他类使用具有相同值的常量),它们将放在那里。

“Fred47”,“ED4”和“ED4”是将在每个方法调用创建的3个String对象。

+0

在第一次方法调用之后,“Fred47”,“ed4”和“ED4”不会在字符串文字池中出现吗?我相信Jared的回答/评论是正确的(+1),因为我所知道的所有编译器都做了这些简单的内联。这就是为什么连接字符串不再非常昂贵(尽管仍然在某种程度上)。 – DaveFar

+1

它是[显然是合法的](http://java.sun.com/docs/books/vmspec/2nd-edition/html/Concepts.doc.html#19124)让JVM在第一次加载时实例化字符串文字来自常量池,而不是在上课时轻松加载它们。我不认为这个实现细节与原始问题真正相关,但这是一个有趣的小问题。 –

+0

注意,如果前两行写成单个语句,那么将直接使用“Fred47”,因为“Fred”+“47”是*编译时常量表达式*。此外,可能会对库进行不同的实现,并生成不同数量的字符串对象。 –

2

方案往往含有大量的字符串文字在他们的代码。在Java中,为了提高效率,这些常量被收集在一个称为字符串表的东西中。例如,如果在十个不同的地方使用字符串"Name: ",则JVM(通常)只有该String的一个实例,并且在使用该字符串的所有十个位置中,引用都指向该实例。这节省了内存。

这种优化是可能的,因为String不可变的。如果可以更改字符串,将其更改为一个地方意味着它会改变其他九个字符串。这就是为什么任何改变String的操作都会返回一个新的实例。这就是为什么如果你这样做:

String s = "boink"; 
s.toUpperCase(); 
System.out.println(s); 

它打印boink,不BOINK

现在有一个更靠谱一点:在相同的基础char[]他们的字符数据java.lang.String可能点的多个实例,换句话说,也可以是不同的同char[]意见,只用一个片的阵列。再次,对效率进行优化。 substring()方法是发生这种情况的一种情况。

s1 = "Fred47"; 

//String s1: data=[ 'F', 'r', 'e', 'd', '4', '7'], offset=0, length=6 
//     ^........................^ 

s2 = s1.substring(2, 5); 

//String s2: data=[ 'F', 'r', 'e', 'd', '4', '7'], offset=2, length=3 
//        ^.........^ 
// the two strings are sharing the same char[]! 

在你的SCJP的问题,这一切都归结为:

  • 字符串"Fred"从字符串表中获得。
  • 字符串"47"从字符串表中获得。
  • 字符串"Fred47"在方法调用期间创建的。// 1
  • 字符串"ed4"在方法调用期间创建,共享相同的背衬阵列"Fred47" // 2
  • 方法调用过程中创建"ED4"的字符串。 // 3
  • s.toString()不会创建一个新的,它只会返回this

之一的这一切有趣的边缘情况:考虑,如果你有一个很长的字符串会发生什么,例如,从网上取一个网页,让我们假设char[]的长度为2兆字节。如果你拿这substring(0, 4),你会得到一个新的字符串,看起来像它只有四个字符长,但它仍然共享这两兆字节的支持数据。这在现实世界中并不常见,但它可能会造成巨大的内存浪费!在(罕见)情况下,如果遇到此问题,可以使用new String(hugeString.substring(0, 4))创建一个带有新的小型后备阵列的字符串。

最后,可以在运行时通过调用intern()来强制字符串进入字符串表。这种情况下的基本规则:不要这样做。扩展规则:除非您使用内存分析器来确定它是一种有用的优化,否则不要这样做。

+0

这是不正确的。没有一个字符串常量是从“字符串池”来的,它们都来自类常量池。如果在几个类中使用像“Fred47”这样的相同字面常量,则它们中的每一个在其常量池中都具有相同的常量。查看Chip McCormick答案的字节码。 –

2

基于javap输出,它看起来像在concocuation期间创建了一个StringBuilder,而不是一个String。然后有三个字符串调用substring(),toUpperCase()和toString()。

最后一次调用不是多余的,因为它将StringBuilder转换为字符串。

>javap -c Test 
Compiled from "Test.java" 

public java.lang.String makinStrings(); 
Code: 
0: ldc  #5; //String Fred 
2: astore_1 
3: new  #6; //class java/lang/StringBuilder 
6: dup 
7: invokespecial #7; //Method java/lang/StringBuilder."<init>":()V 
10: aload_1 
11: invokevirtual #8; //Method java/lang/StringBuilder.append: (Ljava/lang/String;)Ljava/lang/StringBuilder; 
14: ldc  #9; //String 47 
16: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 
19: invokevirtual #10; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 
22: astore_1 
23: aload_1 
24: iconst_2 
25: iconst_5 
26: invokevirtual #11; //Method java/lang/String.substring:(II)Ljava/lang/String; 
29: astore_1 
30: aload_1 
31: invokevirtual #12; //Method java/lang/String.toUpperCase:()Ljava/lang/String; 
34: astore_1 
35: aload_1 
36: invokevirtual #13; //Method java/lang/String.toString:()Ljava/lang/String; 
39: areturn 

}

+0

所以正确的答案是:3.还是我错了? –