有多少个对象?
它会创建 2 个字符串,一个在堆中,另一个在字符串池中吗?
当您编写"Cat" 时,您最终会使用Cat 填充池,并且"Cat" 调用会从池中加载此对象。这通常在编译时就已经发生了。然后new String(...) 将创建一个新的字符串对象,完全忽略池。
所以这个 sn-p 导致了两个对象的创建。为了消除您的困惑,请考虑以下示例:
String first = "Cat";
String second = "Cat";
String third = "Cat";
String fourth = new String("Cat");
在这里,也创建了两个对象。所有的"Cat" 调用都会从池中加载字符串,所以first == second == third 和fourth 将是它自己的对象,因为它使用了new,这总是会导致创建一个新对象,绕过任何类型的缓存机制。
对象是在堆上创建还是在栈上创建并没有真正定义。内存管理完全取决于 JVM。
字符串池详细信息
对于大多数 Java 实现,字符串池已在编译期间创建和填充。当您编写"Cat" 时,编译器会将代表Cat 的字符串对象放入此池中,并且代码中的"Cat" 将通过从池中加载此对象来替换。当你反汇编一个编译的程序时,你可以很容易地看到这一点。例如源码:
public class Test {
public static void main(String[] args) {
String foo = "Hello World";
}
}
反汇编(javap -v):
Classfile /C:/Users/Zabuza/Desktop/Test.class
Last modified 30.03.2021; size 277 bytes
SHA-256 checksum 83de8a7326af14fc95fb499af090f9b3377c56f79f2e78b34e447d66b645a285
Compiled from "Test.java"
public class Test
minor version: 0
major version: 59
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #9 // Test
super_class: #2 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "<init>":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = String #8 // Hello World
#8 = Utf8 Hello World
#9 = Class #10 // Test
#10 = Utf8 Test
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 main
#14 = Utf8 ([Ljava/lang/String;)V
#15 = Utf8 SourceFile
#16 = Utf8 Test.java
{
public Test();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=1
0: ldc #7 // String Hello World
2: astore_1
3: return
LineNumberTable:
line 3: 0
line 4: 3
}
SourceFile: "Test.java"
如你所见,有
#7 = String #8 // Hello World
#8 = Utf8 Hello World
并且方法中的部分被替换为
0: ldc #7
从字符串池中加载Hello World。
字符串实习
字符串实习生方法的目的是什么?
嗯,它使您可以将字符串替换为池中的版本。并用您的字符串填充池,以防它以前不存在。例如:
String first = "hello"; // from pool
String second = new String("hello"); // new object
String third = second.intern(); // from pool, same as first
System.out.println(first == second); // false
System.out.println(first == third); // true
用例
不过,我还没有看到此功能的实际应用程序。
但是,我可以想到一个用例,您可以在应用程序中动态创建长字符串,并且您知道它们稍后会再次出现。然后,您可以将字符串放入池中,以便在以后再次出现时减少内存占用。
假设您从 HTTP 响应中接收到一些长字符串,并且您知道响应在大多数情况下是完全相同的,并且您还希望将它们收集到 List:
private List<String> responses = new ArrayList<>();
...
public void receiveResponse(String response) {
...
responses.add(response);
}
如果不进行实习,您最终会将每个字符串实例(包括重复项)保留在您的记忆中。但是,如果你实习他们,你就不会在内存中有重复的字符串对象:
public void receiveResponse(String response) {
...
String responseFromPool = response.intern();
responses.add(responseFromPool);
}
当然,这有点做作,因为您也可以在这里使用Set。