String.intern()
是一个 native(本地)方法,常应用于在字符串优化和内存管理方面。这个方法的作用是将当前字符串对象放到 Java 字符串常量池中(如果它还不在池中),并返回常量池中该字符串的引用。如果字符串常量池中已经包含了一个等于此 String
对象的字符串,则返回代表池中这个字符串的 String
对象的引用。
这里有几个关键点需要理解:
-
字符串常量池:Java 虚拟机(JVM)为了优化字符串存储,会维护一个特殊的存储区域,称为字符串常量池。这个池中的字符串是唯一的,即任何两个字符串如果内容相同,那么它们实际上是同一个对象。字符串常量池中的字符串可以是编译时常量,也可以是运行时常量(通过
String#intern()
方法添加)。 -
intern()
方法的行为:- 如果调用
intern()
方法的字符串对象已经存在于字符串常量池中,则返回常量池中该字符串的引用(注意,这是已经存在的字符串对象的引用,而不是当前对象的引用)。 - 如果调用
intern()
方法的字符串对象不在字符串常量池中,那么该字符串对象将被添加到字符串常量池中,并返回该字符串对象的引用(此时,当前对象引用和返回的引用是相同的,因为当前对象已经被添加到了常量池中)。
- 如果调用
-
性能优化:通过
intern()
方法,Java 应用程序可以减少字符串对象的数量,特别是当应用程序中使用了大量重复的字符串字面量时。因为所有重复的字符串都将引用字符串常量池中的同一个对象,这有助于减少内存占用和提高性能。 -
使用场景:
intern()
方法在需要比较大量字符串是否相等时特别有用,因为可以直接比较字符串对象的引用(如果它们已经被intern()
处理过且相等),这比比较字符串内容要快得多。 -
注意:从 Java 7 开始,字符串常量池被移动到了堆内存中,而不是之前的永久代(PermGen space)。这一变化使得字符串常量池能够随着 Java 堆的大小动态扩展。
举个例子帮助理解一下
public class StringInternExample {
public static void main(String[] args) {
// 使用字面量创建字符串,它会自动被添加到字符串常量池中
String str1 = "hello";
// 使用 new 关键字创建字符串对象,它不会在常量池中创建,而是在堆上创建
String str2 = new String("hello");
// 调用 intern() 方法,如果常量池中已存在 "hello",则返回常量池中的引用;否则,将 str2 添加到常量池中并返回其引用
String str3 = str2.intern();
// 比较 str1 和 str2
// 因为 str1 是字面量创建的,它直接指向常量池中的字符串;而 str2 是通过 new 创建的,它指向堆上的对象
// 所以 str1 != str2(它们引用不同的对象)
System.out.println(str1 == str2); // 输出:false
// 比较 str1 和 str3
// 因为 str3 是通过 intern() 方法从常量池中获取的 "hello" 的引用,而 str1 也是指向常量池中的 "hello"
// 所以 str1 == str3(它们引用相同的对象)
System.out.println(str1 == str3); // 输出:true
// 验证 str1 和 str2 的内容是否相同
// 尽管它们引用不同的对象,但内容相同
System.out.println(str1.equals(str2)); // 输出:true
}
}
解释
- 字面量 vs. new 关键字:
String str1 = "hello";
这行代码使用字面量创建了一个字符串,这个字符串在编译时就被添加到了字符串常量池中。String str2 = new String("hello");
这行代码使用new
关键字创建了一个新的字符串对象,这个对象被分配在堆上,而不是常量池中。同时,它也会检查常量池中是否已存在内容相同的字符串,但即使存在,也不会复用,而是会创建一个新的对象。
- intern() 方法:
String str3 = str2.intern();
这行代码调用了intern()
方法。由于常量池中已经存在内容为 "hello" 的字符串(由str1
引入),因此intern()
方法不会添加新的字符串到常量池中,而是直接返回常量池中已存在的字符串的引用。因此,str3
和str1
引用的是同一个对象。
- 比较操作:
str1 == str2
比较的是两个对象的引用地址,由于它们指向不同的对象(一个在常量池,一个在堆上),所以结果为false
。str1 == str3
同样比较的是引用地址,但由于str3
是通过intern()
方法从常量池中获取的引用,与str1
指向同一个对象,所以结果为true
。str1.equals(str2)
比较的是两个字符串的内容,由于它们的内容都是 "hello",所以结果为true
。