Java字符串拼接的字符串池运作原理及示例代码疑问解析
Java 字符串拼接的String Pool工作原理与认知纠正
测试代码示例
13: String a = ""; 14: a += 2; 15: a += 'c'; 16: a += false; 17: if ( a == "2cfalse") System.out.println("=="); 18: if ( a.equals("2cfalse")) System.out.println("equals");
程序最终输出仅为equals。
你之前的认知存在几处核心偏差
- 对第13行
String a = "";的创建逻辑理解错误:空字符串""是编译期可确定的字面量,类加载阶段就会被创建并放入String Pool,a持有的是池内对象的引用,并非在堆上随机创建的游离对象。 - 对
+=拼接的执行流程理解错误:Java从JDK 5开始,这类字符串+=操作不会直接每次新建String对象完成拼接,而是被编译期翻译成StringBuilder的链式调用逻辑,最终通过StringBuilder.toString()在普通堆内存生成新的String实例,全程不会触发字符串池的入池流程。 - 对拼接结果的内容时序记错:第14行
a += 2;拼接完成后a的值是"2"而非你提到的"2c",第15行拼上字符'c'才会得到"2c",直到第16行拼上false才会得到内容为"2cfalse"的字符串。 - 对池内
"2cfalse"的来源理解错误:字符串池里的"2cfalse"根本不是你动态拼接生成的对象,它是你第17行代码里写的字面量,在javac编译阶段就被识别为常量,类加载时就被创建放进了String Pool,和你前面通过拼接生成的a指向的对象完全独立。
逐行拆解代码的真实执行流程
- 第13行:类加载完成后,String Pool中已经存在内容为""的常量对象,a直接持有这个池对象的引用。
- 第14行:JVM执行拼接时,先创建一个StringBuilder实例,将a当前指向的空字符串作为初始内容,调用
append(2)追加数字2的字符形式,最终调用toString()在普通堆上生成内容为"2"的String实例,将a的引用指向这个堆对象。此时这个"2"的堆对象不会被放入字符串池。之前a指向的池内空字符串因为是常量对象,也不会被GC回收。 - 第15行:重复上面的拼接逻辑,创建新的StringBuilder,初始内容为a当前指向的堆上的"2",append字符'c',最终在堆上生成内容为"2c"的String实例,a指向这个新堆对象,之前的"2"堆对象失去引用,进入GC候选状态。
- 第16行:同样通过StringBuilder完成
false的追加,最终在堆上生成内容为"2cfalse"的String实例,a指向这个堆上的游离对象,全程没有任何入池操作。 - 第17行:代码中的字面量
"2cfalse"在类加载阶段就已经作为常量存在于String Pool中,和a指向的堆上同内容对象是两个完全独立的内存实例,因为内存地址不同,==的判断结果必然为false,只有两个对象的内容完全一致,所以equals判断为true,最终只打印"equals"。
字符串入池的核心规则
只有两类String对象会被纳入String Pool管理:
- 编译期就能确定固定内容的字符串字面量,比如代码中直接写的
"abc"、"2cfalse",这类对象会在类加载阶段自动创建并放入池内。 - 手动调用
String.intern()方法的堆上字符串:调用该方法时如果池内不存在和当前对象内容一致的常量,就会把当前堆对象的引用放入池;如果已经存在同内容常量,就直接返回池内对象的引用。
你通过StringBuilder动态拼接生成的字符串,默认都是在普通堆内存分配的实例,和池内的同内容常量没有任何引用关联,除非手动调用intern()入池,否则永远不会和池内的字面量为同一个对象。
内容的提问来源于stack exchange,提问作者S. Tiss
相关产品推荐
相关产品推荐

