6

我有以下 Java 代码:

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。

4

3 回答 3

15

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

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

于 2011-09-10T08:50:30.257 回答
2

程序往往在其代码中包含大量字符串文字。在 Java 中,为了提高效率,这些常量被收集在称为字符串表的东西中。例如,如果您"Name: "在十个不同的地方使用字符串,JVM(通常)只有一个该字符串的实例,并且在所有十个使用它的地方,引用都指向那个实例。这样可以节省内存。

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

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

它打印boink,不是BOINK

现在还有一个棘手的问题:多个实例java.lang.String 可能指向char[]它们的字符数据的相同底层,换句话说,它们可能是相同的不同视图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"是在方法调用期间创建的,与//2共享相同的后备数组"Fred47"
  • 该字符串"ED4"是在方法调用期间创建的。//3
  • s.toString()不会创建一个新的,它只是返回this

所有这一切的一个有趣的边缘案例:考虑如果你有一个非常长的字符串会发生什么,例如,从互联网上获取的网页,假设它的长度char[]是 2 兆字节。如果你接受这个,你会得到一个看起来只有四个字符长substring(0, 4)的新字符串,但它仍然共享这 2 兆字节的支持数据。这在现实世界中并不常见,但它可能会浪费大量内存!在您遇到此问题的(极少数)情况下,您可以使用新的小型支持数组创建一个字符串。new String(hugeString.substring(0, 4))

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

于 2011-09-10T09:20:51.173 回答
2

根据 javap 输出,在连接期间似乎创建了 StringBuilder,而不是 String。然后为 substring()、toUpperCase() 和 toString() 调用了三个字符串。

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

>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

}

于 2011-09-10T11:16:43.597 回答