目录一、源码分析JDK8成员变量构造函数常见方法二、String常量池String 常量池到底是什么什么字符串会进常量池常量池的核心规则new String() 为什么不会进池JDK 7 以后常量池的重大变化intern() 到底做了什么字符串拼接进不进池总结三、常见面试点String 核心创建 String 的两种方式 和 equals 的区别字符串常量池重点字符串拼接重点String 常用方法核心特点String 为什么不可变StringBuilder 与 StringBuffer 区别String拼接原理String常量池、运行时常量池、Class常量池区别四、常见面试题1、创建对象 常量池2、字符串拼接一、源码分析JDK8成员变量public final class String implements java.io.Serializable, ComparableString, CharSequence { private final char value[]; private int hash; // Default to 0 }public final class Stringpublic任何地方都能用finalString 不能被继承不能写个子类继承它class String字符串类implements实现了三个接口Serializable可以序列化网络传输 / 存文件ComparableString可以比较大小a.compareTo (b)CharSequence字符串的标准接口private final char value[];private外部不能访问、不能修改final数组地址一旦赋值就不能改char value[]真正存字符串内容的字符数组private int hash; // Default to 0缓存哈希值第一次调用hashCode()时计算一次之后直接用不用重复算提升 HashMap 性能构造函数非常的多我们之说下面的常见几种1. 空参构造public String()public String() { this.value .value; }作用创建一个空字符串底层直接复用常量池里空字符串的char[]不新建数组使用String s new String();→ 等价于String s 注意直接写更高效2. 字符串参数构造public String(String original)public String(String original) { this.value original.value; this.hash original.hash; }作用根据已有字符串创建一个新 String 对象底层直接复用原字符串的 char 数组不复制只新建 String 外壳使用String s new String(abc);重点常量池字符串 new会创建两个对象常量池 1 个 堆 1 个日常开发不要这么写直接String s abc;最优3. char 数组完整构造public String(char value[])public String(char value[]) { this.value Arrays.copyOf(value, value.length); }作用把整个 char 数组转成字符串底层复制一份新数组保护原数组不被修改使用char[] arr {a,b,c}; String s new String(arr);4. char 数组截取构造public String(char value[], int offset, int count)public String(char value[], int offset, int count) { // 边界校验offset、count 不能为负、不能越界 this.value Arrays.copyOfRange(value, offset, offsetcount); }作用从 char 数组中截取一段生成字符串参数offset起始下标count截取长度底层复制截取范围的数组严格校验越界使用char[] arr {a,b,c,d}; String s new String(arr, 1, 2); // 从下标1开始取2个 → bc5. StringBuilder 构造public String(StringBuilder builder)public String(StringBuilder builder) { this.value Arrays.copyOf(builder.getValue(), builder.length()); }作用把StringBuilder转成 String最常用底层无锁直接复制数组效率高使用场景拼接字符串后转 StringStringBuilder sb new StringBuilder(); sb.append(a).append(b); String s new String(sb); // → ab对比构造方法核心用途特点new String()空字符串等价 new String(String)复制字符串会创建新对象new String(char[])char 数组转字符串复制数组安全new String(char[],off,len)截取 char 数组常用new String(StringBuilder)拼接后转字符串常用常见方法基础信息获取方法这些方法直接操作value字符数组最简单高效// 返回字符串长度 字符数组长度 public int length() { return value.length; } // 判断是否为空长度为0 public boolean isEmpty() { return value.length 0; } // 获取指定索引的字符越界抛异常 public char charAt(int index) { if ((index 0) || (index value.length)) { throw new StringIndexOutOfBoundsException(index); } return value[index]; }字符 / 字节拷贝方法底层用System.arraycopynative 本地方法速度极快实现数据拷贝// 拷贝字符串到目标字符数组 public void getChars(...) { System.arraycopy(value, 源起始, 目标数组, 目标起始, 长度); } // 字符串转字节数组支持编码 public byte[] getBytes(String charsetName) { return StringCoding.encode(...); }字符串比较方法1. equals ()判断两个字符串内容是否完全相同先判断引用地址是否相同相同直接返回 true再判断类型是否是 String最后逐字符比较public boolean equals(Object anObject) { if (this anObject) return true; // 地址相同直接相等 if (anObject instanceof String) { String another (String) anObject; int n value.length; if (n another.value.length) { // 长度不同直接不等 char[] v1 value; char[] v2 another.value; int i 0; while (n-- ! 0) { // 逐字符比较 if (v1[i] ! v2[i]) return false; i; } return true; } } return false; }2. compareTo()字典序比较逐字符比较 ASCII 码值返回差值3. 其他比较equalsIgnoreCase()忽略大小写比较startsWith()/endsWith()判断开头 / 结尾regionMatches()比较指定区域字符hashCode () 方法String 的哈希算法31 倍哈希法经典高效public int hashCode() { int h hash; if (h 0 value.length 0) { char val[] value; // 公式h 31 * h val[i] for (int i 0; i value.length; i) { h 31 * h val[i]; } hash h; } return h; }为什么用 3131 是质数减少哈希冲突31 * i (i 5) - iJVM 会自动优化运算极快查找字符 / 子串方法1. indexOf ()朴素字符串匹配算法先找首字符再匹配剩余字符效率满足日常使用static int indexOf(...) { char first 目标首字符; // 遍历源数组找到首字符后逐位匹配 }2. lastIndexOf()反向查找逻辑和 indexOf 一致截取、拼接、替换方法1. substring()不修改原字符串创建新 String 对象返回public String substring(int begin, int end) { return new String(value, begin, 长度); }2. concat()字符串拼接拷贝数组 → 追加内容 → 返回新字符串3. replace()替换字符先找到第一个匹配字符再统一替换返回新字符串大小写、去空格方法toLowerCase()/toUpperCase()考虑地区、特殊字符如希腊字母的大小写转换trim()删除首尾 空格的字符空格、制表符、换行符valueOf 系列静态方法将任意类型转为字符串public static String valueOf(int i) { return Integer.toString(i); } public static String valueOf(Object obj) { return obj null ? null : obj.toString(); }安全转换避免空指针intern () 本地方法public native String intern();字符串常量池核心方法调用时将字符串放入常量池返回常量池中的引用 → 用于节省内存实现字符串复用二、String常量池String 常量池到底是什么一句话String 常量池 一块专门放字符串的缓存区域目的复用字符串少创建对象省内存提高效率它是JVM 专门给 String 做的优化什么字符串会进常量池双引号括起来的字符串字面量会自动进常量池String s abc; // 自动进池下面这些绝对不会自动进池new String(abc)字符串拼接a b从文件、配置、数据库、网络读取方法返回的字符串这些都在堆里不进池常量池的核心规则创建字符串前先去池里找有没有相同内容有 → 直接复用池里对象不新建没有 → 创建后放进池里所以String s1 abc; String s2 abc; s1 s2 → true因为复用了同一个对象new String()为什么不会进池String s new String(abc);执行过程abc→ 进常量池new String(...)→在堆里创建一个新对象堆对象 ≠ 池对象abc new String(abc) → falseJDK 7 以后常量池的重大变化JDK 6常量池在永久代PermGen空间小容易 OOMintern()会把字符串复制到常量池JDK 7包括 8、11、17常量池移到堆Heapintern()不再复制对象池里存的是堆对象的引用这就是为什么String s new String(1) new String(1); s.intern(); String s2 11; s s2 → trueJDK8intern()到底做了什么s.intern();作用把当前字符串手动加入常量池规则池中有相同内容 →返回池中的对象池中没有 →把当前对象存入池返回自己intern () 就是让堆字符串也能享受常量池复用字符串拼接进不进池1. 纯常量拼接进池String s a b c;编译器优化成abc→进池2. 变量拼接不进池String s a b;底层new StringBuilder()→堆对象不进池总结abc → 常量池new String(abc) → 堆a b → 堆读取文件/配置/DB → 堆常量池对象 常量池对象 → true堆对象 常量池对象 → false堆对象 堆对象 → false常量池 字符串缓存用来复用对象双引号字面量自动进池new / 拼接 / 读取 → 不进池intern () 手动把堆字符串丢进池 比地址equals 比内容JDK7 常量池在堆里intern 不复制存引用三、常见面试点String 核心String 是不可变类Immutable底层是private final char[] valueJDK9 是 byte []所有字符串操作截取、替换、拼接都不会修改原字符串只会返回新字符串不可变 线程安全 可以安全缓存 可以常量池共享String 一旦创建内容永远不能改改了就是新对象创建 String 的两种方式1. 字面量创建String s abc;特点自动进入字符串常量池重复创建会复用池里对象不新建内存最省、最快2. new 创建String s new String(abc);特点一定在堆里创建新对象不会自动入池即使内容一样也不相等 和 equals 的区别比较地址equals()比较内容规则字面量之间 可以用只要有一个是堆对象new / 读取 / 拼接 大概率 false比较字符串内容永远用 equalsabc abc → true new String(abc) abc → false字符串常量池重点作用复用字符串对象减少内存提高速度什么时候自动入池只有代码里写的双引号字面量会自动入池什么不会自动入池new String()字符串拼接配置文件读取数据库读取网络读取文件读取手动入池intern ()把堆字符串丢进常量池内容相同则复用池里对象目的省内存 让 可以用字符串拼接重点String s a b c;编译期优化 → 直接变成abc→ 入池String s a b;运行期拼接底层 new StringBuilder ()结果一定是堆对象不入池String 常用方法核心特点substring()→ 不修改原串返回新串replace()→ 不修改原串返回新串trim()→ 不修改原串返回新串toLowerCase()→ 不修改原串返回新串hashCode()→ 使用 31 倍哈希算法缓存起来不重复计算String 为什么不可变好处线程安全可以缓存 hashCode可以安全用作 HashMap key可以进常量池共享省内存安全防止被意外篡改StringBuilder 与 StringBuffer 区别Strings a;本质 创建新对象 性能差StringBuilder可变StringBuilder sb new StringBuilder();源码char[] value;append直接修改数组无锁线程不安全性能最高StringBufferpublic synchronized大量同步锁虽然保证了线程安全但是性能低于Builder单线程 使用StringBuilder多线程使用StringBufferString拼接原理String s a b c;编译后结果new StringBuilder() .append(a) .append(b) .append(c) .toString();反编译结果StringBuilder sb new StringBuilder(); sb.append(a); sb.append(b); sb.append(c); return sb.toString();String常量池、运行时常量池、Class常量池区别名称存储内容Class常量池编译后的字面量和符号引用运行时常量池JVM加载类后产生String常量池专门缓存字符串对象四、常见面试题1、创建对象 常量池String s1 java; String s2 java; String s3 new String(java); String s4 new String(java);1.1、s1 s2、s1 s3、s3 s4结果是true/falses1 s2System.out.println(s1 s2);结果true原因String s1 java; String s2 java;字符串字面量java会进入字符串常量池执行 s1 时常量池不存在 java↓创建 java↓s1 指向常量池对象执行 s2 时发现常量池已有 java↓直接复用↓s2 指向同一个对象s1 s2s1 s2比较的是地址所以结果是trues1 s3System.out.println(s1 s3);结果false分析如下String s3 new String(java);执行过程第一步检查常量池java已经存在第二步new String()在堆中创建新的 String 对象源码public String(String original) { this.value original.value; this.hash original.hash; }会创建一个新的 String 外壳对象虽然s1.equals(s3)为true因为比较的是值但是s1 s3比较的是引用地址两个对象所以结果为falses3 s4System.out.println(s3 s4);结果false执行String s3 new String(java); String s4 new String(java);每次new String(...)都会创建新的堆对象因此s3 s4比较的是StringA的地址和StringB的地址所以结果是false1.2、一共创建了几个对象答案3个对象对象1常量池中java对象2产生的堆对象s3new String(java)对象3产生的堆对象s4new String(java)String Pool : 1个 Heap : s3对应对象 1个 s4对应对象 1个 总计 3个对象2、字符串拼接// 代码片段1 String a ab cd; // 代码片段2 String x ab; String y cd; String b x y;两段代码最终a和b是否进入常量池a abcd、b abcd结果分别是什么解释拼接字符串编译期常量和变量拼接底层实现有什么区别String a ab cd;结果a abcd // true编译期发生了什么因为ab、cd都是字面量常量编译器在编译阶段直接优化为String a abcd;这叫常量折叠Constant Folding相当于字节码LDC abcd直接从常量池加载a abcd比较的是同一个常量池对象结果是trueString x ab; String y cd; String b x y;结果b abcd // false为什么虽然x 内容是ab但是x是变量编译器无法保证运行时一定还是ab所以不能做常量折叠编译器会生成String b new StringBuilder() .append(x) .append(y) .toString();JDK8 反编译基本类似StringBuilder sb new StringBuilder(); sb.append(x); sb.append(y); String b sb.toString();执行时String Pool ab cd abcdHeap new StringBuilder() ↓ new String(abcd)注意StringBuilder.toString()返回的是new String(...)新的堆对象不会自动进入常量池因此b abcd变成了堆对象常量池对象结果是false
Java String 全面解析:从源码到常量池,再到面试高频题
发布时间:2026/6/1 21:05:39
目录一、源码分析JDK8成员变量构造函数常见方法二、String常量池String 常量池到底是什么什么字符串会进常量池常量池的核心规则new String() 为什么不会进池JDK 7 以后常量池的重大变化intern() 到底做了什么字符串拼接进不进池总结三、常见面试点String 核心创建 String 的两种方式 和 equals 的区别字符串常量池重点字符串拼接重点String 常用方法核心特点String 为什么不可变StringBuilder 与 StringBuffer 区别String拼接原理String常量池、运行时常量池、Class常量池区别四、常见面试题1、创建对象 常量池2、字符串拼接一、源码分析JDK8成员变量public final class String implements java.io.Serializable, ComparableString, CharSequence { private final char value[]; private int hash; // Default to 0 }public final class Stringpublic任何地方都能用finalString 不能被继承不能写个子类继承它class String字符串类implements实现了三个接口Serializable可以序列化网络传输 / 存文件ComparableString可以比较大小a.compareTo (b)CharSequence字符串的标准接口private final char value[];private外部不能访问、不能修改final数组地址一旦赋值就不能改char value[]真正存字符串内容的字符数组private int hash; // Default to 0缓存哈希值第一次调用hashCode()时计算一次之后直接用不用重复算提升 HashMap 性能构造函数非常的多我们之说下面的常见几种1. 空参构造public String()public String() { this.value .value; }作用创建一个空字符串底层直接复用常量池里空字符串的char[]不新建数组使用String s new String();→ 等价于String s 注意直接写更高效2. 字符串参数构造public String(String original)public String(String original) { this.value original.value; this.hash original.hash; }作用根据已有字符串创建一个新 String 对象底层直接复用原字符串的 char 数组不复制只新建 String 外壳使用String s new String(abc);重点常量池字符串 new会创建两个对象常量池 1 个 堆 1 个日常开发不要这么写直接String s abc;最优3. char 数组完整构造public String(char value[])public String(char value[]) { this.value Arrays.copyOf(value, value.length); }作用把整个 char 数组转成字符串底层复制一份新数组保护原数组不被修改使用char[] arr {a,b,c}; String s new String(arr);4. char 数组截取构造public String(char value[], int offset, int count)public String(char value[], int offset, int count) { // 边界校验offset、count 不能为负、不能越界 this.value Arrays.copyOfRange(value, offset, offsetcount); }作用从 char 数组中截取一段生成字符串参数offset起始下标count截取长度底层复制截取范围的数组严格校验越界使用char[] arr {a,b,c,d}; String s new String(arr, 1, 2); // 从下标1开始取2个 → bc5. StringBuilder 构造public String(StringBuilder builder)public String(StringBuilder builder) { this.value Arrays.copyOf(builder.getValue(), builder.length()); }作用把StringBuilder转成 String最常用底层无锁直接复制数组效率高使用场景拼接字符串后转 StringStringBuilder sb new StringBuilder(); sb.append(a).append(b); String s new String(sb); // → ab对比构造方法核心用途特点new String()空字符串等价 new String(String)复制字符串会创建新对象new String(char[])char 数组转字符串复制数组安全new String(char[],off,len)截取 char 数组常用new String(StringBuilder)拼接后转字符串常用常见方法基础信息获取方法这些方法直接操作value字符数组最简单高效// 返回字符串长度 字符数组长度 public int length() { return value.length; } // 判断是否为空长度为0 public boolean isEmpty() { return value.length 0; } // 获取指定索引的字符越界抛异常 public char charAt(int index) { if ((index 0) || (index value.length)) { throw new StringIndexOutOfBoundsException(index); } return value[index]; }字符 / 字节拷贝方法底层用System.arraycopynative 本地方法速度极快实现数据拷贝// 拷贝字符串到目标字符数组 public void getChars(...) { System.arraycopy(value, 源起始, 目标数组, 目标起始, 长度); } // 字符串转字节数组支持编码 public byte[] getBytes(String charsetName) { return StringCoding.encode(...); }字符串比较方法1. equals ()判断两个字符串内容是否完全相同先判断引用地址是否相同相同直接返回 true再判断类型是否是 String最后逐字符比较public boolean equals(Object anObject) { if (this anObject) return true; // 地址相同直接相等 if (anObject instanceof String) { String another (String) anObject; int n value.length; if (n another.value.length) { // 长度不同直接不等 char[] v1 value; char[] v2 another.value; int i 0; while (n-- ! 0) { // 逐字符比较 if (v1[i] ! v2[i]) return false; i; } return true; } } return false; }2. compareTo()字典序比较逐字符比较 ASCII 码值返回差值3. 其他比较equalsIgnoreCase()忽略大小写比较startsWith()/endsWith()判断开头 / 结尾regionMatches()比较指定区域字符hashCode () 方法String 的哈希算法31 倍哈希法经典高效public int hashCode() { int h hash; if (h 0 value.length 0) { char val[] value; // 公式h 31 * h val[i] for (int i 0; i value.length; i) { h 31 * h val[i]; } hash h; } return h; }为什么用 3131 是质数减少哈希冲突31 * i (i 5) - iJVM 会自动优化运算极快查找字符 / 子串方法1. indexOf ()朴素字符串匹配算法先找首字符再匹配剩余字符效率满足日常使用static int indexOf(...) { char first 目标首字符; // 遍历源数组找到首字符后逐位匹配 }2. lastIndexOf()反向查找逻辑和 indexOf 一致截取、拼接、替换方法1. substring()不修改原字符串创建新 String 对象返回public String substring(int begin, int end) { return new String(value, begin, 长度); }2. concat()字符串拼接拷贝数组 → 追加内容 → 返回新字符串3. replace()替换字符先找到第一个匹配字符再统一替换返回新字符串大小写、去空格方法toLowerCase()/toUpperCase()考虑地区、特殊字符如希腊字母的大小写转换trim()删除首尾 空格的字符空格、制表符、换行符valueOf 系列静态方法将任意类型转为字符串public static String valueOf(int i) { return Integer.toString(i); } public static String valueOf(Object obj) { return obj null ? null : obj.toString(); }安全转换避免空指针intern () 本地方法public native String intern();字符串常量池核心方法调用时将字符串放入常量池返回常量池中的引用 → 用于节省内存实现字符串复用二、String常量池String 常量池到底是什么一句话String 常量池 一块专门放字符串的缓存区域目的复用字符串少创建对象省内存提高效率它是JVM 专门给 String 做的优化什么字符串会进常量池双引号括起来的字符串字面量会自动进常量池String s abc; // 自动进池下面这些绝对不会自动进池new String(abc)字符串拼接a b从文件、配置、数据库、网络读取方法返回的字符串这些都在堆里不进池常量池的核心规则创建字符串前先去池里找有没有相同内容有 → 直接复用池里对象不新建没有 → 创建后放进池里所以String s1 abc; String s2 abc; s1 s2 → true因为复用了同一个对象new String()为什么不会进池String s new String(abc);执行过程abc→ 进常量池new String(...)→在堆里创建一个新对象堆对象 ≠ 池对象abc new String(abc) → falseJDK 7 以后常量池的重大变化JDK 6常量池在永久代PermGen空间小容易 OOMintern()会把字符串复制到常量池JDK 7包括 8、11、17常量池移到堆Heapintern()不再复制对象池里存的是堆对象的引用这就是为什么String s new String(1) new String(1); s.intern(); String s2 11; s s2 → trueJDK8intern()到底做了什么s.intern();作用把当前字符串手动加入常量池规则池中有相同内容 →返回池中的对象池中没有 →把当前对象存入池返回自己intern () 就是让堆字符串也能享受常量池复用字符串拼接进不进池1. 纯常量拼接进池String s a b c;编译器优化成abc→进池2. 变量拼接不进池String s a b;底层new StringBuilder()→堆对象不进池总结abc → 常量池new String(abc) → 堆a b → 堆读取文件/配置/DB → 堆常量池对象 常量池对象 → true堆对象 常量池对象 → false堆对象 堆对象 → false常量池 字符串缓存用来复用对象双引号字面量自动进池new / 拼接 / 读取 → 不进池intern () 手动把堆字符串丢进池 比地址equals 比内容JDK7 常量池在堆里intern 不复制存引用三、常见面试点String 核心String 是不可变类Immutable底层是private final char[] valueJDK9 是 byte []所有字符串操作截取、替换、拼接都不会修改原字符串只会返回新字符串不可变 线程安全 可以安全缓存 可以常量池共享String 一旦创建内容永远不能改改了就是新对象创建 String 的两种方式1. 字面量创建String s abc;特点自动进入字符串常量池重复创建会复用池里对象不新建内存最省、最快2. new 创建String s new String(abc);特点一定在堆里创建新对象不会自动入池即使内容一样也不相等 和 equals 的区别比较地址equals()比较内容规则字面量之间 可以用只要有一个是堆对象new / 读取 / 拼接 大概率 false比较字符串内容永远用 equalsabc abc → true new String(abc) abc → false字符串常量池重点作用复用字符串对象减少内存提高速度什么时候自动入池只有代码里写的双引号字面量会自动入池什么不会自动入池new String()字符串拼接配置文件读取数据库读取网络读取文件读取手动入池intern ()把堆字符串丢进常量池内容相同则复用池里对象目的省内存 让 可以用字符串拼接重点String s a b c;编译期优化 → 直接变成abc→ 入池String s a b;运行期拼接底层 new StringBuilder ()结果一定是堆对象不入池String 常用方法核心特点substring()→ 不修改原串返回新串replace()→ 不修改原串返回新串trim()→ 不修改原串返回新串toLowerCase()→ 不修改原串返回新串hashCode()→ 使用 31 倍哈希算法缓存起来不重复计算String 为什么不可变好处线程安全可以缓存 hashCode可以安全用作 HashMap key可以进常量池共享省内存安全防止被意外篡改StringBuilder 与 StringBuffer 区别Strings a;本质 创建新对象 性能差StringBuilder可变StringBuilder sb new StringBuilder();源码char[] value;append直接修改数组无锁线程不安全性能最高StringBufferpublic synchronized大量同步锁虽然保证了线程安全但是性能低于Builder单线程 使用StringBuilder多线程使用StringBufferString拼接原理String s a b c;编译后结果new StringBuilder() .append(a) .append(b) .append(c) .toString();反编译结果StringBuilder sb new StringBuilder(); sb.append(a); sb.append(b); sb.append(c); return sb.toString();String常量池、运行时常量池、Class常量池区别名称存储内容Class常量池编译后的字面量和符号引用运行时常量池JVM加载类后产生String常量池专门缓存字符串对象四、常见面试题1、创建对象 常量池String s1 java; String s2 java; String s3 new String(java); String s4 new String(java);1.1、s1 s2、s1 s3、s3 s4结果是true/falses1 s2System.out.println(s1 s2);结果true原因String s1 java; String s2 java;字符串字面量java会进入字符串常量池执行 s1 时常量池不存在 java↓创建 java↓s1 指向常量池对象执行 s2 时发现常量池已有 java↓直接复用↓s2 指向同一个对象s1 s2s1 s2比较的是地址所以结果是trues1 s3System.out.println(s1 s3);结果false分析如下String s3 new String(java);执行过程第一步检查常量池java已经存在第二步new String()在堆中创建新的 String 对象源码public String(String original) { this.value original.value; this.hash original.hash; }会创建一个新的 String 外壳对象虽然s1.equals(s3)为true因为比较的是值但是s1 s3比较的是引用地址两个对象所以结果为falses3 s4System.out.println(s3 s4);结果false执行String s3 new String(java); String s4 new String(java);每次new String(...)都会创建新的堆对象因此s3 s4比较的是StringA的地址和StringB的地址所以结果是false1.2、一共创建了几个对象答案3个对象对象1常量池中java对象2产生的堆对象s3new String(java)对象3产生的堆对象s4new String(java)String Pool : 1个 Heap : s3对应对象 1个 s4对应对象 1个 总计 3个对象2、字符串拼接// 代码片段1 String a ab cd; // 代码片段2 String x ab; String y cd; String b x y;两段代码最终a和b是否进入常量池a abcd、b abcd结果分别是什么解释拼接字符串编译期常量和变量拼接底层实现有什么区别String a ab cd;结果a abcd // true编译期发生了什么因为ab、cd都是字面量常量编译器在编译阶段直接优化为String a abcd;这叫常量折叠Constant Folding相当于字节码LDC abcd直接从常量池加载a abcd比较的是同一个常量池对象结果是trueString x ab; String y cd; String b x y;结果b abcd // false为什么虽然x 内容是ab但是x是变量编译器无法保证运行时一定还是ab所以不能做常量折叠编译器会生成String b new StringBuilder() .append(x) .append(y) .toString();JDK8 反编译基本类似StringBuilder sb new StringBuilder(); sb.append(x); sb.append(y); String b sb.toString();执行时String Pool ab cd abcdHeap new StringBuilder() ↓ new String(abcd)注意StringBuilder.toString()返回的是new String(...)新的堆对象不会自动进入常量池因此b abcd变成了堆对象常量池对象结果是false