题目:String 的设计,用到了哪些设计模式?
难度:high
标签:arch/design-pattern, general/knowledge
评分:⭐☆☆☆☆
String 用到的设计模式我没什么印象,那么我们来分析下 string 的特性来推导下,首先 string 的第一个特性是不可变,另外 string 还有常量的特性,创建出来但不可变队形会被放入常量空间进行复用;string 对象自身是通过 byte 数组或者 char 数组实现的,在不同的jdk 版本中;所有 string 对象都已一种模式的方法,那么是否算模板方法模式?
优点:主动推导分析(没有直接放弃),提到不可变、常量池、字节数组实现、JDK 版本差异
严重不足:未识别享元模式(String 最经典的设计模式);误判模板方法模式;未说不可变模式(final class + final 字段 + 无 setter);未说代理模式(StringBuilder 作为可变代理、JDK 9+ 内部 StringLatin1/StringUTF16 代理存储);未说 String 不可变的四个好处
补充:三大模式——享元(常量池复用)、不可变(线程安全+哈希稳定)、代理(StringBuilder+JDK9 内部存储)
核心考点定位:能否识别出 String 里的享元模式(最核心)、不可变模式、代理模式,并理解它们的设计动机。
知识点拆解:
String s = "hello";)自动入池s.intern() 手动入池new String("hello") 创建堆对象,不入池(除非调 intern)"hello" == "hello" 为 true(引用相等),不同变量共享同一个对象public final class String:类不可继承private final byte[] value:字段不可变concat、substring、toUpperCase)new String(String original) 会复制底层数组)hashCode() 可缓存,HashMap key 性能更好System.getProperty 返回值、可作方法参数不被外部修改toString() 转回不可变 StringStringLatin1/StringUTF16:String 内部用这两个紧凑存储类作为代理,节省内存new String(byte[]):把外部数组的引用代理进 String,并做防御性拷贝== vs equals:常量池内 == 可能为 true,堆对象 == 为 falsenew String("a") 和 "a" 不是同一个对象(前者在堆,后者在常量池)intern() 在 JDK 7+ 之后常量池移到了堆中按「底层原理 → 设计权衡 → 工程实践」递进排列。
考察方向:
延伸递进面试题:
"a" 和 new String("a") 是同一个对象吗?调 intern() 后呢?new 在堆,引用不同;调 intern() 后如果常量池已有就返回常量池引用,否则把堆对象加入常量池byte[] + coder 字段?char[](2 字节/字符),JDK 9 用 byte[] + coder 区分 Latin1(1 字节)和 UTF16(2 字节),Latin1 字符场景内存减半面试官您好,String 的设计主要用到三个设计模式:享元模式、不可变模式、代理模式。
第一个是享元模式,这是 String 最经典的设计。享元的核心思想是"相同对象共享"。在 String 里的体现就是字符串常量池:字面量 "hello" 会自动放入常量池,第二次写 "hello" 直接复用同一个对象。效果就是 "hello" == "hello" 为 true,多个变量引用同一个对象,节省大量内存。除了字面量入池,还有 s.intern() 手动入池——new String("hello").intern() 会返回常量池里的引用。
第二个是不可变模式(Immutable Object Pattern)。String 是不可变对象,体现在:类是 public final 不能继承;底层 private final byte[] value 字段不可变;没有 setter,所有"修改"方法如 concat、substring、toUpperCase 都返回新对象;构造器还做防御性拷贝,防止外部修改内部数组。
不可变带来四个好处:
hashCode() 可以缓存,作 HashMap 的 key 性能更好代价是每次修改都要新建对象,所以频繁拼字符串要用 StringBuilder。
第三个是代理模式。String 的不可变性催生了两类代理:
第一类是 StringBuilder/Buffer 作为可变代理——频繁修改字符串的场景,先用 StringBuilder 拼接,最后 toString() 转回不可变 String。
第二类是 JDK 9+ 的 StringLatin1 和 StringUTF16——String 内部用这两个紧凑存储类作为代理,通过 coder 字段区分 Latin1(1 字节)和 UTF16(2 字节)。JDK 8 用 char[] 每个字符固定 2 字节,JDK 9 改成 byte[] + coder,Latin1 字符场景内存减半。
还有一个常见陷阱:"a" 在常量池,new String("a") 在堆,两者引用不同,== 为 false;但调 intern() 后,如果常量池已有相同内容,就返回常量池的引用,== 变 true。
总结一句话:String 是"享元模式 + 不可变模式 + 代理模式"的典型组合,既节省内存,又保证了线程安全和哈希稳定性。
这就是我对 String 设计的理解。