String intern方法与普通字符串创建的区别及使用疑问
咱们一步一步拆解你对String.intern()的困惑,先从你给出的代码示例说起:
String s1 = "vivek"; //Stmt 1 String s2 = "vivek"; //Stmt 2 String s3 = s1.intern(); //Stmt 3 System.out.println(s1 == s2);// prints true System.out.println(s1 == s3);// prints true
先搞清楚编译器的自动intern是怎么回事
当你用双引号直接声明字符串字面量(比如"vivek")时,Java编译器会自动帮你完成两件事:
- 检查字符串常量池(JVM里专门存储唯一字符串实例的区域)中是否已经存在内容为
"vivek"的字符串。 - 如果不存在,就把这个字面量对应的字符串实例放入常量池;如果已经存在,就直接让变量指向常量池里的这个实例。
所以你的Stmt1执行时,常量池里没有"vivek",就把它放进去,s1指向常量池的这个实例。Stmt2执行时,编译器发现常量池里已经有"vivek"了,直接让s2指向同一个实例——这就是为什么s1 == s2返回true,因为它们引用的是同一个对象。
编译器自动intern针对的是哪个对象?
编译器自动执行intern操作的是所有用双引号直接定义的字符串字面量,不是针对某个变量。换句话说,只要你写了"xxx"这种形式的字符串,编译器都会先处理它的常量池逻辑,不管它是直接赋值给变量,还是作为参数传递。
不是“编译器为s1执行intern来创建s2”,而是s1和s2对应的字面量"vivek"都被编译器提前处理过,最终都指向了常量池里的同一个实例。
Stmt2和Stmt3的区别是什么?两者始终相同吗?
在你的代码里,两者效果看起来一样,但本质上是不同的逻辑:
- Stmt2是编译器自动处理的字面量入池,变量直接指向常量池实例。
- Stmt3是主动调用intern()方法,手动触发字符串的入池逻辑。
只有当s1本身已经指向常量池实例时(比如你的代码里s1是字面量),s1.intern()才会返回s1本身,这时候s1==s3才为true。但如果s1是在堆中创建的字符串(比如用new String()或者字符串拼接),两者的区别就很明显了:
举个不同场景的例子:
// 用new创建的字符串,会在堆里生成一个新实例,同时常量池里会有"vivek"(因为字面量被编译器处理了) String s1 = new String("vivek"); String s2 = "vivek"; // 指向常量池的实例 String s3 = s1.intern(); // 把s1的内容入池(这里常量池已经有了,所以返回常量池的引用) System.out.println(s1 == s2); // false,s1是堆对象,s2是常量池对象 System.out.println(s1 == s3); // false,s3指向常量池对象 System.out.println(s2 == s3); // true
这个例子里,Stmt2和Stmt3的结果就不一样了——Stmt2直接拿常量池的引用,Stmt3是把堆对象的内容关联到常量池,然后返回常量池的引用。
intern()方法的特殊之处在哪里?
intern()的核心价值是给你手动控制字符串入池的能力,这是编译器自动处理做不到的:
- 处理非字面量创建的字符串:比如用
new String()、字符串拼接("vi" + "vek"是编译器优化,但new String("vi") + "vek"是堆对象)得到的字符串,编译器不会自动把它们放入常量池,这时候调用intern()就能把它们的内容纳入常量池,后续可以复用同一个实例。 - 节省内存:常量池里的字符串是唯一的,重复的内容只会存储一份。如果你的程序里有大量重复的字符串(比如从数据库读取的重复数据),调用intern()可以大幅减少内存占用。
- 注意性能代价:intern()是native方法,而且常量池的操作是线程安全的(有锁机制),如果频繁调用可能会影响性能,所以要根据实际场景权衡使用。
内容的提问来源于stack exchange,提问作者Vivek Vardhan

