intern() 是 Java 中 String 类提供的一个native本地方法(底层由C/C++实现),它的核心作用是优化字符串内存存储、保证字符串常量的唯一性。
一、前置知识:字符串的存储位置
在讲解 intern() 之前,先明确 Java 中字符串的两个核心存储区域(JDK 7 及以后版本,常量池位置有调整):
- 字符串常量池(String Pool) :属于堆内存的一部分(JDK 6 及以前在方法区/永久代),用于存储唯一的字符串常量,相同内容的字符串在常量池中只会存在一份。
- 堆内存(Heap) :用于存储通过
new关键字创建的字符串对象,每一次new都会生成一个新的对象(即使内容相同)。
两种字符串创建方式的差异:
java
// 方式1:字面量赋值,直接从常量池获取(不存在则创建并放入常量池)
String s1 = "abc";
// 方式2:new关键字创建,堆中生成新对象(常量池若无"abc",会先创建常量池版本)
String s2 = new String("abc");
// 验证:s1指向常量池,s2指向堆,引用不同
System.out.println(s1 == s2); // 输出:false
二、intern() 方法的核心定义与工作原理
1. 核心定义
intern() 方法的作用是:将当前字符串对象的引用(或副本)存入字符串常量池,并返回字符串常量池中该字符串的引用。
2. 分两种场景工作(核心)
intern() 的行为取决于字符串常量池中是否已经存在与当前字符串内容相同 (通过 equals() 判断相等)的字符串:
场景1:常量池中已存在相同内容的字符串
- 行为:不会在常量池中创建新对象/引用,直接返回常量池中已有字符串的引用。
- 示例:
java
String s1 = "abc"; // 常量池创建"abc",s1指向常量池
String s2 = new String("abc"); // 堆中创建新对象,s2指向堆
// 调用intern():常量池已有"abc",直接返回常量池引用
String s3 = s2.intern();
// 验证:s3与s1均指向常量池,引用相同
System.out.println(s1 == s3); // 输出:true
System.out.println(s2 == s3); // 输出:false(s2仍指向堆)
场景2:常量池中不存在相同内容的字符串
- 行为(JDK 7+ 主流版本):直接将堆中当前字符串对象的引用存入常量池,不复制字符串内容,也不创建新对象,最终返回该引用(常量池与堆指向同一个对象)。
- 行为(JDK 6 及以前):复制堆中字符串的内容,在常量池中创建一个新的字符串对象,返回常量池中新对象的引用(堆对象与常量池对象相互独立)。
- 示例(JDK 7+):
java
// 拼接字符串(这种方式不会自动将"ab"放入常量池)
String s1 = new String("a") + new String("b");
// 此时常量池中无"ab",intern()将s1的堆引用存入常量池,返回该引用
String s2 = s1.intern();
// 字面量"ab":直接从常量池获取(即s1的堆引用)
String s3 = "ab";
// 验证:三者指向同一个堆对象
System.out.println(s1 == s2); // 输出:true
System.out.println(s1 == s3); // 输出:true
三、关键注意点
1. intern() 有返回值,需接收返回值才有效
intern() 不会修改调用者本身的引用指向,只会返回常量池中的引用。如果不接收返回值,调用 intern() 几乎没有意义:
java
String s = new String("abc");
s.intern(); // 未接收返回值,s仍指向堆中的对象
String sConstant = "abc";
System.out.println(s == sConstant); // 输出:false(无效调用)
// 正确用法:接收intern()返回值,获取常量池引用
String sInterned = s.intern();
System.out.println(sInterned == sConstant); // 输出:true
2. JDK 6 与 JDK 7+ 的核心差异(重点)
| 版本 | 常量池位置 | 常量池无对应字符串时的行为 | 堆与常量池引用关系 |
|---|---|---|---|
| JDK 6 | 方法区/永久代(与堆分离) | 复制堆中字符串内容,创建新的常量池对象 | 堆对象与常量池对象相互独立(== 为 false) |
| JDK 7+ | 堆内存(独立分区) | 直接存储堆中字符串对象的引用 | 常量池引用指向堆对象(== 为 true) |
3. intern() 的核心价值
- 节省内存空间 :对于大量重复的字符串(如日志中的相同字段、业务中的固定编码),调用
intern()后可保证常量池中只存储一份,避免堆中创建大量重复对象,减少内存占用。 - 提高字符串比较效率 :常量池中的字符串可通过
==直接比较引用(无需调用equals()比较内容),效率更高。
四、完整代码示例(JDK 7+ 运行)
java
public class StringInternDemo {
public static void main(String[] args) {
// 场景1:常量池已存在相同字符串
String constantStr = "hello";
String heapStr = new String("hello");
String internedStr1 = heapStr.intern();
System.out.println("场景1:");
System.out.println(constantStr == heapStr); // false(常量池 vs 堆)
System.out.println(constantStr == internedStr1); // true(均指向常量池)
// 场景2:常量池不存在相同字符串
String heapStr2 = new String("hi") + new String("java");
String internedStr2 = heapStr2.intern();
String constantStr2 = "hijava";
System.out.println("\n场景2:");
System.out.println(heapStr2 == internedStr2); // true(常量池存储堆引用)
System.out.println(heapStr2 == constantStr2); // true(字面量指向堆引用)
}
}
运行结果(JDK 7+)
场景1:
false
true
场景2:
true
true
总结
intern()是String类的 native 方法,核心作用是将字符串纳入常量池管理,保证唯一性。- 常量池已有对应字符串时,直接返回常量池引用;无对应字符串时(JDK 7+),存储堆对象引用并返回。
- 必须接收
intern()的返回值,才能获取常量池中的有效引用。 - 适合大量重复字符串场景,可节省内存、提高比较效率。