[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site-settings":3,"\u002Farticles\u002Fdesign-pattern-12":26,"\u002Farticles\u002Fdesign-pattern-12\u002Frelated":43,"\u002Farticles\u002Fdesign-pattern-12\u002Fadjacent":72,"\u002Fcategories":84,"render-design-pattern-12":110},{"code":4,"message":5,"data":6},0,"ok",{"author_city":7,"author_years":8,"site_tagline":9,"site_description":10,"skill_one_desc":11,"author_status":12,"skill_two_title":13,"about_hero_image":9,"skill_one_title":14,"icp":15,"author_role":16,"og_default_image":9,"closing_quote_date":17,"toolkit":18,"skill_two_desc":19,"author_bio":9,"site_footer":9,"site_url":20,"author_tagline":21,"weekly_thought":9,"tagline_line_one":22,"author_bio_two":9,"closing_quote":23,"site_name":24,"tagline_line_two":25,"home_hero_image":9},"西安","10","","花开空白 - 个人博客","熟悉Java、Python、Go等后端语言。","努力工作中","前端开发","后端开发","陕ICP备2021009682号-1","开发者","2022年·秋","Java,JavaScript,TypeScript,Python,Go","熟悉Js、Ts语言以及Vue、uniapp等框架。","https:\u002F\u002Firule.cn","很高兴遇见你。","把复杂的事，","把每次一发布，都当成一次和陌生人的握手。","花开空白","写成简单的故事。",{"code":4,"message":5,"data":27},{"id":28,"slug":29,"title":30,"summary":31,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":34,"likeCount":4,"wordCount":35,"readingMinutes":36,"publishedAt":37,"contentMd":38,"contentHtml":39,"tags":40,"metaTitle":9,"metaDescription":9,"keywords":9,"canonicalUrl":9,"robots":41,"ogImage":9,"faq":42,"aiSummary":42},"2113931783249596417","design-pattern-12","设计模式修炼之路 ⑫ 享元模式——共享细粒度对象","共享细粒度对象、内部状态与外部状态的切分、Integer 缓存与字符串常量池","2113897628365230081",1,2866,6956,18,"2024-04-15T17:46:59","上一篇外观模式给它留了句实在话：前面几位省的都是\"代码的复杂度\"，它省的是\"货真价实的内存\"。今天正主登场：**享元模式（Flyweight Pattern）**，GoF 23 人名单正牌选手、结构型阵营只剩它和代理压轴。Flyweight 是个自造词——fly（飞）+ weight（重量），合起来\"轻如飞絮\"：**同样的对象要造一万份？留一份共享，其余全指过来**。它回答的问题朴素到心酸：对象太多，内存不够用了，怎么办？\n\n## 一、什么是享元模式？\n\n先看定义：\n\n> 运用共享技术有效地支持大量细粒度的对象。\n\n翻译成人话：**当系统里出现海量小对象、而它们大部分内容彼此相同时，把\"相同的部分\"抽出来做成有限几个共享对象，\"不同的部分\"在调用时再传进去**。一万个对象瘦身成 N 个对象加一万份参数，内存从\"万级\"降到\"常数级\"。\n\n生活类比是共享单车停车场——不对，单车会被骑走，更准确的类比是**景区地图上的印章**：盖一百个\"到此一游\"，不存在一百枚印章，只有一枚，盖一百次；每次位置不同（你手按哪），印章本身不变。印章是共享的享元，位置是随用随传的参数。\n\n它有四个角色：\n\n- **Flyweight（享元接口）**：本篇里 GoPiece 的公开方法 place(x, y)；\n- **ConcreteFlyweight（具体享元）**：真正被共享的对象，只装**内部状态**，本篇里是黑棋、白棋两个实例；\n- **FlyweightFactory（享元工厂）**：按内部状态缓存享元，保证\"同一状态全 JVM 一份\"，本篇里是 PieceFactory；\n- **UnsharedConcreteFlyweight（非共享享元）**：不参与共享的享元，本篇用不上，知道有这一角色即可。\n\n而全模式的灵魂，是它对状态的这刀切分：\n\n- **内部状态（Intrinsic）**：不随环境变化、可以共享的部分——围棋里棋子的**颜色和纹理**。黑棋的纹理和另一颗黑棋的纹理一模一样，没理由存两份；\n- **外部状态（Extrinsic）**：随环境变化、无法共享的部分——围棋里棋子的**坐标**。(3,3) 的黑棋和 (15,15) 的黑棋，差异全在坐标上。\n\n切完之后享元就成型了：**内部状态装进对象、全 JVM 一份；外部状态从对象里赶出去、每次调用当参数传入**。上一篇外观是\"把流程从客户端收进门面\"，这一篇是\"把变化从对象里赶出去\"——两位干的是同一门手艺：把\"多\"变成\"一\"。\n\n## 二、享元模式的优缺点\n\n### 优点\n\n1. **内存立竿见影**：对象数量从\"实例数\"降到\"内部状态组合数\"。一盘围棋 300 颗子，棋子对象只需要 2 个——省的不是 30%，是 99%；\n2. **创建与 GC 压力同步下降**：对象不重复造了，年轻代的分配速率和垃圾回收频率跟着降——高并发场景里这往往比省内存本身还值钱；\n3. **结构稳定**：享元不可变，天生线程安全，并发共享零加锁。\n\n### 缺点\n\n1. **设计成本前置**：哪些字段能共享、哪些必须外置，得先想清楚——切错一刀（把可变字段留在享元里），多个\"逻辑调用方\"就会通过同一对象互相踩脚；\n2. **API 变啰嗦**：外部状态从字段变成参数，原来 `piece.place()` 一个字都不用传，现在 `piece.place(x, y)`——每个调用点都得多背一份\"本该对象自己记的事\"；\n3. **生命周期常驻**：工厂的缓存通常静态常驻，内部状态种类一旦膨胀（比如键设计不当导致缓存无限增长），享元池自己反而变成内存泄漏点。\n\n## 三、享元模式的应用场景\n\n- **包装类缓存（JDK）**：Integer、Long、Short、Byte、Character、Boolean 的 valueOf 全走缓存——Integer 缓存 -128 到 127，Boolean 就 TRUE\u002FFALSE 两个。这是 JDK 官方实装的最大规模享元池，第五节专门解剖。\n- **字符串常量池（JVM）**：`\"hello\"` 字面量全 JVM 存一份，两个类里写同一个字符串，指向的是同一块内存——你从第一天写 Java 起就在用享元。\n- **枚举**：JVM 保证每个枚举常量全 JVM 只实例化一次，天然享元——所以枚举天生适合当\"种类\"用（单例模式那篇聊过它还是最硬的单例写法）。\n- **POI 的 CellStyle**：用 Apache POI 写过 Excel 的都撞过那堵墙——每个单元格 new 一个 CellStyle，写到几万行报\"样式表超限\"，官方文档明晃晃写着\"请复用样式对象\"。CellStyle 就是被 Excel 格式限定了数量的享元。\n- **棋牌类游戏**：一副斗地主 54 张牌，服务端管一万桌，\"黑桃 A\"这个对象需要一万份吗？一份就够，哪桌出了它、压在谁的出牌序列里，全是外部状态。\n- **地图 POI \u002F GUI 图标**：十万个餐馆图标，样式按品类就几十种——图标是享元，经纬度是外部状态。\n\n**两个高频误认，先纠个偏**：\n\n1. **数据库连接池、线程池不是享元，是对象池（Object Pool）**。分水岭在\"共享方式\"：享元是**同一个对象被并发共享、且不可变**（黑白棋被全盘同时引用）；对象池是**同一个对象被轮流独占、且可变**（连接借走用完还回来，池里同一时刻只有一个线程在用它）。一个像公共雕塑，一个像共享充电宝。\n2. **单例也不是享元**。单例是\"整个类只要一个实例\"，享元是\"每种内部状态一个实例\"——享元池里通常躺着好几个对象（黑白两颗棋），池子本身就是个\"按状态分组的批量单例\"。\n\n**什么时候该用**：对象数量大（万级起步）、且大部分内部状态重复、且重复部分是大头（内存大头在纹理不在坐标）——三条全中再上享元；只有一万个十几字节的 POJO？GC 表示毫无压力，别折腾。\n\n## 四、享元模式的实现方式\n\n示例代码在 design-pattern 项目的 flyweight 包里：\n\n```\ndesign-pattern\u002Fsrc\u002Fmain\u002Fjava\n└── flyweight               \u002F\u002F 享元模式\n    ├── BadPiece.java       \u002F\u002F 反面教材：每颗棋子自带颜色+纹理+坐标\n    ├── GoPiece.java        \u002F\u002F 享元：只装内部状态（颜色、纹理），坐标外部传入\n    ├── PieceFactory.java   \u002F\u002F 享元工厂：按颜色缓存，全 JVM 一份\n    └── FlyweightTest.java  \u002F\u002F 测试入口\n```\n\n场景：围棋。棋子有颜色（黑\u002F白）、每颗棋子渲染要一份 1MB 的纹理、还要落在某个坐标上。先看\"一颗棋子一个对象\"的下场，再看享元怎么救。\n\n### 1. 反面教材：每颗棋子一份纹理\n\n最直觉的建模：落一子 new 一颗棋子，颜色、纹理、坐标全塞对象里：\n\n```java\n\u002F\u002F 反面教材：每颗棋子一个对象，颜色、纹理、坐标全塞在对象里\npublic class BadPiece {\n    private final String color;\n    \u002F\u002F 模拟渲染纹理：真正的内存大户（实际项目里可能是图片、字形、网格模型）\n    private final int[] texture = new int[256 * 1024]; \u002F\u002F 1MB\n    private final int x;\n    private final int y;\n\n    public BadPiece(String color, int x, int y) {\n        this.color = color;\n        this.x = x;\n        this.y = y;\n    }\n\n    public void place() {\n        System.out.println(\"[BadPiece] \" + color + \"棋落在 (\" + x + \", \" + y + \")，自带一份 1MB 纹理\");\n    }\n}\n```\n\n模拟下十手棋：\n\n```java\nList\u003CBadPiece> board = new ArrayList\u003C>();\nfor (int i = 0; i \u003C 10; i++) {\n    BadPiece piece = new BadPiece(i % 2 == 0 ? \"黑\" : \"白\", i, i * 2);\n    board.add(piece);\n    piece.place();\n}\nSystem.out.println(\"10 手棋 = \" + board.size() + \" 个对象 = 10 份 1MB 纹理\");\nSystem.out.println(\"一盘 300 手的棋就是 300 个对象、300MB 纹理——而所有黑棋的纹理，本来就一模一样\");\n```\n\n运行结果：\n\n```\n===== 反面教材：每颗棋子自带一份纹理 =====\n[BadPiece] 黑棋落在 (0, 0)，自带一份 1MB 纹理\n[BadPiece] 白棋落在 (1, 2)，自带一份 1MB 纹理\n[BadPiece] 黑棋落在 (2, 4)，自带一份 1MB 纹理\n[BadPiece] 白棋落在 (3, 6)，自带一份 1MB 纹理\n[BadPiece] 黑棋落在 (4, 8)，自带一份 1MB 纹理\n[BadPiece] 白棋落在 (5, 10)，自带一份 1MB 纹理\n[BadPiece] 黑棋落在 (6, 12)，自带一份 1MB 纹理\n[BadPiece] 白棋落在 (7, 14)，自带一份 1MB 纹理\n[BadPiece] 黑棋落在 (8, 16)，自带一份 1MB 纹理\n[BadPiece] 白棋落在 (9, 18)，自带一份 1MB 纹理\n10 手棋 = 10 个对象 = 10 份 1MB 纹理\n一盘 300 手的棋就是 300 个对象、300MB 纹理——而所有黑棋的纹理，本来就一模一样\n```\n\n能跑，但账没法看：\n\n1. **重复度触目惊心**：十个对象里其实只有两种内容——黑棋、白棋。颜色加纹理只有 2 种组合，却造了 10 个对象、10 份纹理；一盘棋 300 颗子，300MB 纹理里 298MB 是纯重复；\n2. **真正的差异被淹没了**：十颗棋子彼此唯一不同的是坐标——几个字节的信息，却拖着 1MB 的车身；\n3. **对象数随对局线性膨胀**：每落一子堆一个对象，一场多桌并发的棋牌室，内存曲线直接跟着在线人数起飞。\n\n病根只有一个：**\"不变的 1MB\"和\"多变的几个字节\"被塞进了同一个对象，重复的部分跟着实例数一起翻倍**。\n\n### 2. 享元版：一份纹理，全盘共享\n\n先动刀切状态——纹理和颜色留下（内部状态），坐标赶出去（外部状态）：\n\n```java\n\u002F\u002F 享元：颜色和纹理是内部状态（不变、全盘共享），坐标是外部状态（用时传入）\npublic class GoPiece {\n    private final String color;\n    \u002F\u002F 纹理只需两份：黑一份、白一份，全盘所有同色棋子共享这同一份\n    private final int[] texture = new int[256 * 1024]; \u002F\u002F 1MB\n\n    public GoPiece(String color) {\n        this.color = color;\n    }\n\n    \u002F\u002F 坐标 x、y 是外部状态：每次落子由调用方传入，享元对象自己不保存\n    public void place(int x, int y) {\n        System.out.println(\"[GoPiece] \" + color + \"棋落在 (\" + x + \", \" + y + \")，纹理共享全盘同一份\");\n    }\n}\n```\n\n注意两个细节：字段全是 final——**享元被全盘共享，必须不可变**，哪个调用方手贱改了纹理，全盘棋子跟着变脸；place 的参数 x、y——**每颗棋子落哪由调用方告诉它**，对象自己\"失忆\"了。\n\n再配工厂，保证\"同一颜色全 JVM 一份\"：\n\n```java\n\u002F\u002F 享元工厂：按内部状态（颜色）缓存享元，保证每种颜色全 JVM 只有一份\npublic class PieceFactory {\n    private static final Map\u003CString, GoPiece> CACHE = new HashMap\u003C>();\n\n    public static GoPiece get(String color) {\n        GoPiece piece = CACHE.get(color);\n        if (piece == null) {\n            piece = new GoPiece(color);\n            CACHE.put(color, piece);\n            System.out.println(\"[工厂] 第一次要\" + color + \"棋，创建并缓存（此后全盘复用）\");\n        }\n        return piece;\n    }\n}\n```\n\n同样的四手棋：\n\n```java\n\u002F\u002F 黑白各只有一份，坐标作为外部状态在调用时传入\nPieceFactory.get(\"黑\").place(3, 3);\nPieceFactory.get(\"白\").place(15, 15);\nPieceFactory.get(\"黑\").place(16, 3);\nPieceFactory.get(\"白\").place(2, 2);\n\nGoPiece black1 = PieceFactory.get(\"黑\");\nGoPiece black2 = PieceFactory.get(\"黑\");\nSystem.out.println(\"两次取到的黑棋是同一个对象吗？\" + (black1 == black2));\nSystem.out.println(\"全盘 300 手也一样：对象数永远 2 个，纹理永远 2MB\");\n```\n\n运行结果：\n\n```\n===== 正解：享元模式 =====\n[工厂] 第一次要黑棋，创建并缓存（此后全盘复用）\n[GoPiece] 黑棋落在 (3, 3)，纹理共享全盘同一份\n[工厂] 第一次要白棋，创建并缓存（此后全盘复用）\n[GoPiece] 白棋落在 (15, 15)，纹理共享全盘同一份\n[GoPiece] 黑棋落在 (16, 3)，纹理共享全盘同一份\n[GoPiece] 白棋落在 (2, 2)，纹理共享全盘同一份\n两次取到的黑棋是同一个对象吗？true\n全盘 300 手也一样：对象数永远 2 个，纹理永远 2MB\n```\n\n对照反面教材逐条验收：\n\n- 对象数与落子数**彻底脱钩**：反面教材 300 手 300 个对象，正解 300 手还是 2 个——内存从 O(手数) 降到 O(颜色数)，这正是定义里\"支持大量细粒度对象\"的实现方式：大量对象被\"一份享元 + 每次调用的外部状态\"撑起来了；\n- `black1 == black2` 为 true——工厂真的只发同一份对象，连\"==\"都验明正身；\n- 谁该记住坐标？**棋盘**。真实实现里落子记录是棋盘上的一个 `List\u003CPlacement>`（每条只存颜色引用 + 坐标），棋盘管外部状态、享元管内部状态，职责各归其位；\n- 纹理这种重资源，从\"随对象生\"变成\"随类别生\"——加载一次，全盘受益，连加载耗时都省了。\n\n## 五、Integer 缓存与字符串常量池：JDK 里的享元\n\n理论讲完，去看这套机制在 JDK 里的样子——先看一段让无数人怀疑人生的代码：\n\n```java\nInteger a = 127;\nInteger b = 127;\nInteger c = 128;\nInteger d = 128;\nSystem.out.println(a == b);   \u002F\u002F true\nSystem.out.println(c == d);   \u002F\u002F false\n```\n\n同一个写法，127 相等、128 不等，玄机全在 Integer.valueOf 的源码里：\n\n```java\npublic static Integer valueOf(int i) {\n    if (i >= IntegerCache.low && i \u003C= IntegerCache.high)\n        return IntegerCache.cache[i + (-IntegerCache.low)];  \u002F\u002F 命中缓存，返回共享对象\n    return new Integer(i);                                    \u002F\u002F 超范围，每次真 new\n}\n```\n\nIntegerCache 是个静态内部类，类加载时就把 -128 到 127 这 256 个 Integer **提前 new 好、放进数组**——一个提前造好的享元池，PieceFactory 需要时才创建，IntegerCache 是\"先造好等你来取\"，思路一脉相承。`Integer a = 127` 的自动装箱编译成 `Integer.valueOf(127)`，命中缓存，a 和 b 拿到的是**同一个对象**，所以 == 为 true；128 超出范围，c 和 d 各自 new，== 自然 false。\n\n这就是各大编码规范反复强调\"包装类比较必须用 equals\"的技术根源——**享元缓存的存在，让 == 的结果跟数值范围挂钩**，这种语义陷阱只能靠规范绕开。顺带两个冷知识：缓存上限可以用 `-XX:AutoBoxCacheMax=\u003Csize>` 调大（下限 -128 动不了）；Long、Short、Byte、Character 也有各自的缓存，只是范围写死、不能调。\n\n字符串那边是另一副景象。`String s = \"hello\"` 的字面量进**字符串常量池**，两个类里写同一个字面量，指向池里同一份：\n\n```java\nString s1 = \"享元\";\nString s2 = \"享元\";\nSystem.out.println(s1 == s2);          \u002F\u002F true：常量池里就一份\nSystem.out.println(s1 == new String(\"享元\")); \u002F\u002F false：new 出来的不走池\nSystem.out.println(s1 == new String(\"享元\").intern()); \u002F\u002F true：intern() 把它送回池里对齐\n```\n\n和 IntegerCache 比，常量池是\"运行时也能进进出出的享元池\"——intern() 就是手动登记处：池里有就返回池里那份，没有就登记当前这份。字符串不可变（String 全字段 final），恰好满足享元\"共享必须不可变\"的铁律——JDK 这两套享元，一套提前造、一套随时进，把\"对象复用\"做进了语言的地基。\n\n## 写在最后\n\n回顾这一篇：享元模式把对象劈成两半——内部状态（不变、共享）留在对象里，外部状态（多变）赶出去做参数，配一个工厂保证\"同一状态全 JVM 一份\"，海量对象瘦身成有限几个；反面教材\"一颗棋子一份纹理\"300MB 的账单，享元两行字抹平；包装类缓存、字符串常量池、枚举、POI 的 CellStyle、棋牌服务端的牌对象，全是它的地盘；享元 vs 对象池的分水岭是\"并发共享的不可变对象\"还是\"轮流独占的可变对象\"——连接池线程池都不算享元。\n\n下一篇 ⑬ 代理模式，结构型最后一位压轴出场。它的 UML 和装饰器几乎一个模子：同样实现同一个接口、同样手里攥着一个同类对象——区别全在意图上：装饰器回答\"怎么给它加功能\"，代理回答\"怎么替它管住访问\"。懒加载、权限校验、远程调用，还有 JDK 动态代理、CGLIB、Spring AOP 这整座大厦的地基，全是这一篇的内容。","\u003Cp>上一篇外观模式给它留了句实在话：前面几位省的都是\"代码的复杂度\"，它省的是\"货真价实的内存\"。今天正主登场：\u003Cstrong>享元模式（Flyweight Pattern）\u003C\u002Fstrong>，GoF 23 人名单正牌选手、结构型阵营只剩它和代理压轴。Flyweight 是个自造词——fly（飞）+ weight（重量），合起来\"轻如飞絮\"：\u003Cstrong>同样的对象要造一万份？留一份共享，其余全指过来\u003C\u002Fstrong>。它回答的问题朴素到心酸：对象太多，内存不够用了，怎么办？\u003C\u002Fp>\n\u003Ch2>一、什么是享元模式？\u003C\u002Fh2>\n\u003Cp>先看定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>运用共享技术有效地支持大量细粒度的对象。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>当系统里出现海量小对象、而它们大部分内容彼此相同时，把\"相同的部分\"抽出来做成有限几个共享对象，\"不同的部分\"在调用时再传进去\u003C\u002Fstrong>。一万个对象瘦身成 N 个对象加一万份参数，内存从\"万级\"降到\"常数级\"。\u003C\u002Fp>\n\u003Cp>生活类比是共享单车停车场——不对，单车会被骑走，更准确的类比是\u003Cstrong>景区地图上的印章\u003C\u002Fstrong>：盖一百个\"到此一游\"，不存在一百枚印章，只有一枚，盖一百次；每次位置不同（你手按哪），印章本身不变。印章是共享的享元，位置是随用随传的参数。\u003C\u002Fp>\n\u003Cp>它有四个角色：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>Flyweight（享元接口）\u003C\u002Fstrong>：本篇里 GoPiece 的公开方法 place(x, y)；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>ConcreteFlyweight（具体享元）\u003C\u002Fstrong>：真正被共享的对象，只装\u003Cstrong>内部状态\u003C\u002Fstrong>，本篇里是黑棋、白棋两个实例；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>FlyweightFactory（享元工厂）\u003C\u002Fstrong>：按内部状态缓存享元，保证\"同一状态全 JVM 一份\"，本篇里是 PieceFactory；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>UnsharedConcreteFlyweight（非共享享元）\u003C\u002Fstrong>：不参与共享的享元，本篇用不上，知道有这一角色即可。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>而全模式的灵魂，是它对状态的这刀切分：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>内部状态（Intrinsic）\u003C\u002Fstrong>：不随环境变化、可以共享的部分——围棋里棋子的\u003Cstrong>颜色和纹理\u003C\u002Fstrong>。黑棋的纹理和另一颗黑棋的纹理一模一样，没理由存两份；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>外部状态（Extrinsic）\u003C\u002Fstrong>：随环境变化、无法共享的部分——围棋里棋子的\u003Cstrong>坐标\u003C\u002Fstrong>。(3,3) 的黑棋和 (15,15) 的黑棋，差异全在坐标上。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>切完之后享元就成型了：\u003Cstrong>内部状态装进对象、全 JVM 一份；外部状态从对象里赶出去、每次调用当参数传入\u003C\u002Fstrong>。上一篇外观是\"把流程从客户端收进门面\"，这一篇是\"把变化从对象里赶出去\"——两位干的是同一门手艺：把\"多\"变成\"一\"。\u003C\u002Fp>\n\u003Ch2>二、享元模式的优缺点\u003C\u002Fh2>\n\u003Ch3>优点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>内存立竿见影\u003C\u002Fstrong>：对象数量从\"实例数\"降到\"内部状态组合数\"。一盘围棋 300 颗子，棋子对象只需要 2 个——省的不是 30%，是 99%；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>创建与 GC 压力同步下降\u003C\u002Fstrong>：对象不重复造了，年轻代的分配速率和垃圾回收频率跟着降——高并发场景里这往往比省内存本身还值钱；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>结构稳定\u003C\u002Fstrong>：享元不可变，天生线程安全，并发共享零加锁。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>缺点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>设计成本前置\u003C\u002Fstrong>：哪些字段能共享、哪些必须外置，得先想清楚——切错一刀（把可变字段留在享元里），多个\"逻辑调用方\"就会通过同一对象互相踩脚；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>API 变啰嗦\u003C\u002Fstrong>：外部状态从字段变成参数，原来 \u003Ccode>piece.place()\u003C\u002Fcode> 一个字都不用传，现在 \u003Ccode>piece.place(x, y)\u003C\u002Fcode>——每个调用点都得多背一份\"本该对象自己记的事\"；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>生命周期常驻\u003C\u002Fstrong>：工厂的缓存通常静态常驻，内部状态种类一旦膨胀（比如键设计不当导致缓存无限增长），享元池自己反而变成内存泄漏点。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>三、享元模式的应用场景\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>\u003Cstrong>包装类缓存（JDK）\u003C\u002Fstrong>：Integer、Long、Short、Byte、Character、Boolean 的 valueOf 全走缓存——Integer 缓存 -128 到 127，Boolean 就 TRUE\u002FFALSE 两个。这是 JDK 官方实装的最大规模享元池，第五节专门解剖。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>字符串常量池（JVM）\u003C\u002Fstrong>：\u003Ccode>\"hello\"\u003C\u002Fcode> 字面量全 JVM 存一份，两个类里写同一个字符串，指向的是同一块内存——你从第一天写 Java 起就在用享元。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>枚举\u003C\u002Fstrong>：JVM 保证每个枚举常量全 JVM 只实例化一次，天然享元——所以枚举天生适合当\"种类\"用（单例模式那篇聊过它还是最硬的单例写法）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>POI 的 CellStyle\u003C\u002Fstrong>：用 Apache POI 写过 Excel 的都撞过那堵墙——每个单元格 new 一个 CellStyle，写到几万行报\"样式表超限\"，官方文档明晃晃写着\"请复用样式对象\"。CellStyle 就是被 Excel 格式限定了数量的享元。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>棋牌类游戏\u003C\u002Fstrong>：一副斗地主 54 张牌，服务端管一万桌，\"黑桃 A\"这个对象需要一万份吗？一份就够，哪桌出了它、压在谁的出牌序列里，全是外部状态。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>地图 POI \u002F GUI 图标\u003C\u002Fstrong>：十万个餐馆图标，样式按品类就几十种——图标是享元，经纬度是外部状态。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Cstrong>两个高频误认，先纠个偏\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>数据库连接池、线程池不是享元，是对象池（Object Pool）\u003C\u002Fstrong>。分水岭在\"共享方式\"：享元是\u003Cstrong>同一个对象被并发共享、且不可变\u003C\u002Fstrong>（黑白棋被全盘同时引用）；对象池是\u003Cstrong>同一个对象被轮流独占、且可变\u003C\u002Fstrong>（连接借走用完还回来，池里同一时刻只有一个线程在用它）。一个像公共雕塑，一个像共享充电宝。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>单例也不是享元\u003C\u002Fstrong>。单例是\"整个类只要一个实例\"，享元是\"每种内部状态一个实例\"——享元池里通常躺着好几个对象（黑白两颗棋），池子本身就是个\"按状态分组的批量单例\"。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>\u003Cstrong>什么时候该用\u003C\u002Fstrong>：对象数量大（万级起步）、且大部分内部状态重复、且重复部分是大头（内存大头在纹理不在坐标）——三条全中再上享元；只有一万个十几字节的 POJO？GC 表示毫无压力，别折腾。\u003C\u002Fp>\n\u003Ch2>四、享元模式的实现方式\u003C\u002Fh2>\n\u003Cp>示例代码在 design-pattern 项目的 flyweight 包里：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>design-pattern\u002Fsrc\u002Fmain\u002Fjava\n└── flyweight               \u002F\u002F 享元模式\n    ├── BadPiece.java       \u002F\u002F 反面教材：每颗棋子自带颜色+纹理+坐标\n    ├── GoPiece.java        \u002F\u002F 享元：只装内部状态（颜色、纹理），坐标外部传入\n    ├── PieceFactory.java   \u002F\u002F 享元工厂：按颜色缓存，全 JVM 一份\n    └── FlyweightTest.java  \u002F\u002F 测试入口\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>场景：围棋。棋子有颜色（黑\u002F白）、每颗棋子渲染要一份 1MB 的纹理、还要落在某个坐标上。先看\"一颗棋子一个对象\"的下场，再看享元怎么救。\u003C\u002Fp>\n\u003Ch3>1. 反面教材：每颗棋子一份纹理\u003C\u002Fh3>\n\u003Cp>最直觉的建模：落一子 new 一颗棋子，颜色、纹理、坐标全塞对象里：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 反面教材：每颗棋子一个对象，颜色、纹理、坐标全塞在对象里\npublic class BadPiece {\n    private final String color;\n    \u002F\u002F 模拟渲染纹理：真正的内存大户（实际项目里可能是图片、字形、网格模型）\n    private final int[] texture = new int[256 * 1024]; \u002F\u002F 1MB\n    private final int x;\n    private final int y;\n\n    public BadPiece(String color, int x, int y) {\n        this.color = color;\n        this.x = x;\n        this.y = y;\n    }\n\n    public void place() {\n        System.out.println(\"[BadPiece] \" + color + \"棋落在 (\" + x + \", \" + y + \")，自带一份 1MB 纹理\");\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>模拟下十手棋：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">List&lt;BadPiece&gt; board = new ArrayList&lt;&gt;();\nfor (int i = 0; i &lt; 10; i++) {\n    BadPiece piece = new BadPiece(i % 2 == 0 ? \"黑\" : \"白\", i, i * 2);\n    board.add(piece);\n    piece.place();\n}\nSystem.out.println(\"10 手棋 = \" + board.size() + \" 个对象 = 10 份 1MB 纹理\");\nSystem.out.println(\"一盘 300 手的棋就是 300 个对象、300MB 纹理——而所有黑棋的纹理，本来就一模一样\");\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>===== 反面教材：每颗棋子自带一份纹理 =====\n[BadPiece] 黑棋落在 (0, 0)，自带一份 1MB 纹理\n[BadPiece] 白棋落在 (1, 2)，自带一份 1MB 纹理\n[BadPiece] 黑棋落在 (2, 4)，自带一份 1MB 纹理\n[BadPiece] 白棋落在 (3, 6)，自带一份 1MB 纹理\n[BadPiece] 黑棋落在 (4, 8)，自带一份 1MB 纹理\n[BadPiece] 白棋落在 (5, 10)，自带一份 1MB 纹理\n[BadPiece] 黑棋落在 (6, 12)，自带一份 1MB 纹理\n[BadPiece] 白棋落在 (7, 14)，自带一份 1MB 纹理\n[BadPiece] 黑棋落在 (8, 16)，自带一份 1MB 纹理\n[BadPiece] 白棋落在 (9, 18)，自带一份 1MB 纹理\n10 手棋 = 10 个对象 = 10 份 1MB 纹理\n一盘 300 手的棋就是 300 个对象、300MB 纹理——而所有黑棋的纹理，本来就一模一样\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>能跑，但账没法看：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>重复度触目惊心\u003C\u002Fstrong>：十个对象里其实只有两种内容——黑棋、白棋。颜色加纹理只有 2 种组合，却造了 10 个对象、10 份纹理；一盘棋 300 颗子，300MB 纹理里 298MB 是纯重复；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>真正的差异被淹没了\u003C\u002Fstrong>：十颗棋子彼此唯一不同的是坐标——几个字节的信息，却拖着 1MB 的车身；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>对象数随对局线性膨胀\u003C\u002Fstrong>：每落一子堆一个对象，一场多桌并发的棋牌室，内存曲线直接跟着在线人数起飞。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>病根只有一个：\u003Cstrong>\"不变的 1MB\"和\"多变的几个字节\"被塞进了同一个对象，重复的部分跟着实例数一起翻倍\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Ch3>2. 享元版：一份纹理，全盘共享\u003C\u002Fh3>\n\u003Cp>先动刀切状态——纹理和颜色留下（内部状态），坐标赶出去（外部状态）：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 享元：颜色和纹理是内部状态（不变、全盘共享），坐标是外部状态（用时传入）\npublic class GoPiece {\n    private final String color;\n    \u002F\u002F 纹理只需两份：黑一份、白一份，全盘所有同色棋子共享这同一份\n    private final int[] texture = new int[256 * 1024]; \u002F\u002F 1MB\n\n    public GoPiece(String color) {\n        this.color = color;\n    }\n\n    \u002F\u002F 坐标 x、y 是外部状态：每次落子由调用方传入，享元对象自己不保存\n    public void place(int x, int y) {\n        System.out.println(\"[GoPiece] \" + color + \"棋落在 (\" + x + \", \" + y + \")，纹理共享全盘同一份\");\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>注意两个细节：字段全是 final——\u003Cstrong>享元被全盘共享，必须不可变\u003C\u002Fstrong>，哪个调用方手贱改了纹理，全盘棋子跟着变脸；place 的参数 x、y——\u003Cstrong>每颗棋子落哪由调用方告诉它\u003C\u002Fstrong>，对象自己\"失忆\"了。\u003C\u002Fp>\n\u003Cp>再配工厂，保证\"同一颜色全 JVM 一份\"：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 享元工厂：按内部状态（颜色）缓存享元，保证每种颜色全 JVM 只有一份\npublic class PieceFactory {\n    private static final Map&lt;String, GoPiece&gt; CACHE = new HashMap&lt;&gt;();\n\n    public static GoPiece get(String color) {\n        GoPiece piece = CACHE.get(color);\n        if (piece == null) {\n            piece = new GoPiece(color);\n            CACHE.put(color, piece);\n            System.out.println(\"[工厂] 第一次要\" + color + \"棋，创建并缓存（此后全盘复用）\");\n        }\n        return piece;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>同样的四手棋：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 黑白各只有一份，坐标作为外部状态在调用时传入\nPieceFactory.get(\"黑\").place(3, 3);\nPieceFactory.get(\"白\").place(15, 15);\nPieceFactory.get(\"黑\").place(16, 3);\nPieceFactory.get(\"白\").place(2, 2);\n\nGoPiece black1 = PieceFactory.get(\"黑\");\nGoPiece black2 = PieceFactory.get(\"黑\");\nSystem.out.println(\"两次取到的黑棋是同一个对象吗？\" + (black1 == black2));\nSystem.out.println(\"全盘 300 手也一样：对象数永远 2 个，纹理永远 2MB\");\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>===== 正解：享元模式 =====\n[工厂] 第一次要黑棋，创建并缓存（此后全盘复用）\n[GoPiece] 黑棋落在 (3, 3)，纹理共享全盘同一份\n[工厂] 第一次要白棋，创建并缓存（此后全盘复用）\n[GoPiece] 白棋落在 (15, 15)，纹理共享全盘同一份\n[GoPiece] 黑棋落在 (16, 3)，纹理共享全盘同一份\n[GoPiece] 白棋落在 (2, 2)，纹理共享全盘同一份\n两次取到的黑棋是同一个对象吗？true\n全盘 300 手也一样：对象数永远 2 个，纹理永远 2MB\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>对照反面教材逐条验收：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>对象数与落子数\u003Cstrong>彻底脱钩\u003C\u002Fstrong>：反面教材 300 手 300 个对象，正解 300 手还是 2 个——内存从 O(手数) 降到 O(颜色数)，这正是定义里\"支持大量细粒度对象\"的实现方式：大量对象被\"一份享元 + 每次调用的外部状态\"撑起来了；\u003C\u002Fli>\n \u003Cli>\u003Ccode>black1 == black2\u003C\u002Fcode> 为 true——工厂真的只发同一份对象，连\"==\"都验明正身；\u003C\u002Fli>\n \u003Cli>谁该记住坐标？\u003Cstrong>棋盘\u003C\u002Fstrong>。真实实现里落子记录是棋盘上的一个 \u003Ccode>List&lt;Placement&gt;\u003C\u002Fcode>（每条只存颜色引用 + 坐标），棋盘管外部状态、享元管内部状态，职责各归其位；\u003C\u002Fli>\n \u003Cli>纹理这种重资源，从\"随对象生\"变成\"随类别生\"——加载一次，全盘受益，连加载耗时都省了。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2>五、Integer 缓存与字符串常量池：JDK 里的享元\u003C\u002Fh2>\n\u003Cp>理论讲完，去看这套机制在 JDK 里的样子——先看一段让无数人怀疑人生的代码：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">Integer a = 127;\nInteger b = 127;\nInteger c = 128;\nInteger d = 128;\nSystem.out.println(a == b);   \u002F\u002F true\nSystem.out.println(c == d);   \u002F\u002F false\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>同一个写法，127 相等、128 不等，玄机全在 Integer.valueOf 的源码里：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public static Integer valueOf(int i) {\n    if (i &gt;= IntegerCache.low &amp;&amp; i &lt;= IntegerCache.high)\n        return IntegerCache.cache[i + (-IntegerCache.low)];  \u002F\u002F 命中缓存，返回共享对象\n    return new Integer(i);                                    \u002F\u002F 超范围，每次真 new\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>IntegerCache 是个静态内部类，类加载时就把 -128 到 127 这 256 个 Integer \u003Cstrong>提前 new 好、放进数组\u003C\u002Fstrong>——一个提前造好的享元池，PieceFactory 需要时才创建，IntegerCache 是\"先造好等你来取\"，思路一脉相承。\u003Ccode>Integer a = 127\u003C\u002Fcode> 的自动装箱编译成 \u003Ccode>Integer.valueOf(127)\u003C\u002Fcode>，命中缓存，a 和 b 拿到的是\u003Cstrong>同一个对象\u003C\u002Fstrong>，所以 == 为 true；128 超出范围，c 和 d 各自 new，== 自然 false。\u003C\u002Fp>\n\u003Cp>这就是各大编码规范反复强调\"包装类比较必须用 equals\"的技术根源——\u003Cstrong>享元缓存的存在，让 == 的结果跟数值范围挂钩\u003C\u002Fstrong>，这种语义陷阱只能靠规范绕开。顺带两个冷知识：缓存上限可以用 \u003Ccode>-XX:AutoBoxCacheMax=&lt;size&gt;\u003C\u002Fcode> 调大（下限 -128 动不了）；Long、Short、Byte、Character 也有各自的缓存，只是范围写死、不能调。\u003C\u002Fp>\n\u003Cp>字符串那边是另一副景象。\u003Ccode>String s = \"hello\"\u003C\u002Fcode> 的字面量进\u003Cstrong>字符串常量池\u003C\u002Fstrong>，两个类里写同一个字面量，指向池里同一份：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">String s1 = \"享元\";\nString s2 = \"享元\";\nSystem.out.println(s1 == s2);          \u002F\u002F true：常量池里就一份\nSystem.out.println(s1 == new String(\"享元\")); \u002F\u002F false：new 出来的不走池\nSystem.out.println(s1 == new String(\"享元\").intern()); \u002F\u002F true：intern() 把它送回池里对齐\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>和 IntegerCache 比，常量池是\"运行时也能进进出出的享元池\"——intern() 就是手动登记处：池里有就返回池里那份，没有就登记当前这份。字符串不可变（String 全字段 final），恰好满足享元\"共享必须不可变\"的铁律——JDK 这两套享元，一套提前造、一套随时进，把\"对象复用\"做进了语言的地基。\u003C\u002Fp>\n\u003Ch2>写在最后\u003C\u002Fh2>\n\u003Cp>回顾这一篇：享元模式把对象劈成两半——内部状态（不变、共享）留在对象里，外部状态（多变）赶出去做参数，配一个工厂保证\"同一状态全 JVM 一份\"，海量对象瘦身成有限几个；反面教材\"一颗棋子一份纹理\"300MB 的账单，享元两行字抹平；包装类缓存、字符串常量池、枚举、POI 的 CellStyle、棋牌服务端的牌对象，全是它的地盘；享元 vs 对象池的分水岭是\"并发共享的不可变对象\"还是\"轮流独占的可变对象\"——连接池线程池都不算享元。\u003C\u002Fp>\n\u003Cp>下一篇 ⑬ 代理模式，结构型最后一位压轴出场。它的 UML 和装饰器几乎一个模子：同样实现同一个接口、同样手里攥着一个同类对象——区别全在意图上：装饰器回答\"怎么给它加功能\"，代理回答\"怎么替它管住访问\"。懒加载、权限校验、远程调用，还有 JDK 动态代理、CGLIB、Spring AOP 这整座大厦的地基，全是这一篇的内容。\u003C\u002Fp>",[],"index,follow",null,{"code":4,"message":5,"data":44},[45,54,63],{"id":46,"slug":47,"title":48,"summary":49,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":50,"likeCount":4,"wordCount":51,"readingMinutes":52,"publishedAt":53},"2113930718034792450","design-pattern-02","设计模式修炼之路 ② 单例模式——一个实例，六种写法","一个类在整个应用里只允许存在一个实例——听着是一句话需求，做起来是一路打怪：懒汉式、饿汉式、双重校验锁、静态内部类、枚举，六种写法沿着\"线程安全\"这条主线层层演进。",1708,5238,14,"2023-09-24T10:50:03",{"id":55,"slug":56,"title":57,"summary":58,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":59,"likeCount":4,"wordCount":60,"readingMinutes":61,"publishedAt":62},"2113933638763548673","design-pattern-11","设计模式修炼之路 ⑪ 外观模式——给杂乱子系统一个入口","给杂乱子系统一个统一入口、只编排不干活、SLF4J 名字里的 Facade",3176,7681,20,"2024-03-20T15:49:38",{"id":64,"slug":65,"title":66,"summary":67,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":68,"likeCount":4,"wordCount":69,"readingMinutes":70,"publishedAt":71},"2113931131085656066","design-pattern-03","设计模式修炼之路 ③ 工厂模式——简单工厂、工厂方法、抽象工厂三兄弟","简单工厂、工厂方法、抽象工厂三兄弟的演进与取舍、开闭原则得失",3741,6415,17,"2023-10-13T21:36:05",{"code":4,"message":5,"data":73},{"prev":74,"next":75},{"id":55,"slug":56,"title":57,"summary":58,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":59,"likeCount":4,"wordCount":60,"readingMinutes":61,"publishedAt":62},{"id":76,"slug":77,"title":78,"summary":79,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":80,"likeCount":4,"wordCount":81,"readingMinutes":82,"publishedAt":83},"2113932970753527809","design-pattern-13","设计模式修炼之路 ⑬ 代理模式——控制访问，而非增强","控制访问而非增强、JDK 动态代理的 $Proxy0、CGLIB 与 Spring AOP 的老搭档",3804,9153,23,"2024-05-02T10:29:24",{"code":4,"message":5,"data":85},[86,92,97,103],{"id":87,"name":88,"slug":89,"description":90,"sortOrder":4,"articleCount":91},"1946000000000000001","antlr4","tech","编程与技术笔记","13",{"id":32,"name":93,"slug":94,"description":95,"sortOrder":4,"articleCount":96},"设计模式","design-pattern","设计模式系列","34",{"id":98,"name":99,"slug":100,"description":101,"sortOrder":33,"articleCount":102},"1946000000000000002","随笔","essay","生活与思考","0",{"id":104,"name":105,"slug":106,"description":107,"sortOrder":108,"articleCount":109},"2105200093764698113","工具","tools","好用的工具推荐",2,"1",{"html":111,"toc":112},"\u003Cp>上一篇外观模式给它留了句实在话：前面几位省的都是\"代码的复杂度\"，它省的是\"货真价实的内存\"。今天正主登场：\u003Cstrong>享元模式（Flyweight Pattern）\u003C\u002Fstrong>，GoF 23 人名单正牌选手、结构型阵营只剩它和代理压轴。Flyweight 是个自造词——fly（飞）+ weight（重量），合起来\"轻如飞絮\"：\u003Cstrong>同样的对象要造一万份？留一份共享，其余全指过来\u003C\u002Fstrong>。它回答的问题朴素到心酸：对象太多，内存不够用了，怎么办？\u003C\u002Fp>\n\u003Ch2 id=\"toc-1\">一、什么是享元模式？\u003C\u002Fh2>\n\u003Cp>先看定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>运用共享技术有效地支持大量细粒度的对象。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>当系统里出现海量小对象、而它们大部分内容彼此相同时，把\"相同的部分\"抽出来做成有限几个共享对象，\"不同的部分\"在调用时再传进去\u003C\u002Fstrong>。一万个对象瘦身成 N 个对象加一万份参数，内存从\"万级\"降到\"常数级\"。\u003C\u002Fp>\n\u003Cp>生活类比是共享单车停车场——不对，单车会被骑走，更准确的类比是\u003Cstrong>景区地图上的印章\u003C\u002Fstrong>：盖一百个\"到此一游\"，不存在一百枚印章，只有一枚，盖一百次；每次位置不同（你手按哪），印章本身不变。印章是共享的享元，位置是随用随传的参数。\u003C\u002Fp>\n\u003Cp>它有四个角色：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>Flyweight（享元接口）\u003C\u002Fstrong>：本篇里 GoPiece 的公开方法 place(x, y)；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>ConcreteFlyweight（具体享元）\u003C\u002Fstrong>：真正被共享的对象，只装\u003Cstrong>内部状态\u003C\u002Fstrong>，本篇里是黑棋、白棋两个实例；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>FlyweightFactory（享元工厂）\u003C\u002Fstrong>：按内部状态缓存享元，保证\"同一状态全 JVM 一份\"，本篇里是 PieceFactory；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>UnsharedConcreteFlyweight（非共享享元）\u003C\u002Fstrong>：不参与共享的享元，本篇用不上，知道有这一角色即可。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>而全模式的灵魂，是它对状态的这刀切分：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>内部状态（Intrinsic）\u003C\u002Fstrong>：不随环境变化、可以共享的部分——围棋里棋子的\u003Cstrong>颜色和纹理\u003C\u002Fstrong>。黑棋的纹理和另一颗黑棋的纹理一模一样，没理由存两份；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>外部状态（Extrinsic）\u003C\u002Fstrong>：随环境变化、无法共享的部分——围棋里棋子的\u003Cstrong>坐标\u003C\u002Fstrong>。(3,3) 的黑棋和 (15,15) 的黑棋，差异全在坐标上。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>切完之后享元就成型了：\u003Cstrong>内部状态装进对象、全 JVM 一份；外部状态从对象里赶出去、每次调用当参数传入\u003C\u002Fstrong>。上一篇外观是\"把流程从客户端收进门面\"，这一篇是\"把变化从对象里赶出去\"——两位干的是同一门手艺：把\"多\"变成\"一\"。\u003C\u002Fp>\n\u003Ch2 id=\"toc-2\">二、享元模式的优缺点\u003C\u002Fh2>\n\u003Ch3 id=\"toc-3\">优点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>内存立竿见影\u003C\u002Fstrong>：对象数量从\"实例数\"降到\"内部状态组合数\"。一盘围棋 300 颗子，棋子对象只需要 2 个——省的不是 30%，是 99%；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>创建与 GC 压力同步下降\u003C\u002Fstrong>：对象不重复造了，年轻代的分配速率和垃圾回收频率跟着降——高并发场景里这往往比省内存本身还值钱；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>结构稳定\u003C\u002Fstrong>：享元不可变，天生线程安全，并发共享零加锁。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3 id=\"toc-4\">缺点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>设计成本前置\u003C\u002Fstrong>：哪些字段能共享、哪些必须外置，得先想清楚——切错一刀（把可变字段留在享元里），多个\"逻辑调用方\"就会通过同一对象互相踩脚；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>API 变啰嗦\u003C\u002Fstrong>：外部状态从字段变成参数，原来 \u003Ccode>piece.place()\u003C\u002Fcode> 一个字都不用传，现在 \u003Ccode>piece.place(x, y)\u003C\u002Fcode>——每个调用点都得多背一份\"本该对象自己记的事\"；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>生命周期常驻\u003C\u002Fstrong>：工厂的缓存通常静态常驻，内部状态种类一旦膨胀（比如键设计不当导致缓存无限增长），享元池自己反而变成内存泄漏点。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-5\">三、享元模式的应用场景\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>\u003Cstrong>包装类缓存（JDK）\u003C\u002Fstrong>：Integer、Long、Short、Byte、Character、Boolean 的 valueOf 全走缓存——Integer 缓存 -128 到 127，Boolean 就 TRUE\u002FFALSE 两个。这是 JDK 官方实装的最大规模享元池，第五节专门解剖。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>字符串常量池（JVM）\u003C\u002Fstrong>：\u003Ccode>\"hello\"\u003C\u002Fcode> 字面量全 JVM 存一份，两个类里写同一个字符串，指向的是同一块内存——你从第一天写 Java 起就在用享元。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>枚举\u003C\u002Fstrong>：JVM 保证每个枚举常量全 JVM 只实例化一次，天然享元——所以枚举天生适合当\"种类\"用（单例模式那篇聊过它还是最硬的单例写法）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>POI 的 CellStyle\u003C\u002Fstrong>：用 Apache POI 写过 Excel 的都撞过那堵墙——每个单元格 new 一个 CellStyle，写到几万行报\"样式表超限\"，官方文档明晃晃写着\"请复用样式对象\"。CellStyle 就是被 Excel 格式限定了数量的享元。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>棋牌类游戏\u003C\u002Fstrong>：一副斗地主 54 张牌，服务端管一万桌，\"黑桃 A\"这个对象需要一万份吗？一份就够，哪桌出了它、压在谁的出牌序列里，全是外部状态。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>地图 POI \u002F GUI 图标\u003C\u002Fstrong>：十万个餐馆图标，样式按品类就几十种——图标是享元，经纬度是外部状态。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Cstrong>两个高频误认，先纠个偏\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>数据库连接池、线程池不是享元，是对象池（Object Pool）\u003C\u002Fstrong>。分水岭在\"共享方式\"：享元是\u003Cstrong>同一个对象被并发共享、且不可变\u003C\u002Fstrong>（黑白棋被全盘同时引用）；对象池是\u003Cstrong>同一个对象被轮流独占、且可变\u003C\u002Fstrong>（连接借走用完还回来，池里同一时刻只有一个线程在用它）。一个像公共雕塑，一个像共享充电宝。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>单例也不是享元\u003C\u002Fstrong>。单例是\"整个类只要一个实例\"，享元是\"每种内部状态一个实例\"——享元池里通常躺着好几个对象（黑白两颗棋），池子本身就是个\"按状态分组的批量单例\"。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>\u003Cstrong>什么时候该用\u003C\u002Fstrong>：对象数量大（万级起步）、且大部分内部状态重复、且重复部分是大头（内存大头在纹理不在坐标）——三条全中再上享元；只有一万个十几字节的 POJO？GC 表示毫无压力，别折腾。\u003C\u002Fp>\n\u003Ch2 id=\"toc-6\">四、享元模式的实现方式\u003C\u002Fh2>\n\u003Cp>示例代码在 design-pattern 项目的 flyweight 包里：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>design-pattern\u002Fsrc\u002Fmain\u002Fjava\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>└── flyweight               \u002F\u002F 享元模式\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── BadPiece.java       \u002F\u002F 反面教材：每颗棋子自带颜色+纹理+坐标\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── GoPiece.java        \u002F\u002F 享元：只装内部状态（颜色、纹理），坐标外部传入\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── PieceFactory.java   \u002F\u002F 享元工厂：按颜色缓存，全 JVM 一份\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    └── FlyweightTest.java  \u002F\u002F 测试入口\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>场景：围棋。棋子有颜色（黑\u002F白）、每颗棋子渲染要一份 1MB 的纹理、还要落在某个坐标上。先看\"一颗棋子一个对象\"的下场，再看享元怎么救。\u003C\u002Fp>\n\u003Ch3 id=\"toc-7\">1. 反面教材：每颗棋子一份纹理\u003C\u002Fh3>\n\u003Cp>最直觉的建模：落一子 new 一颗棋子，颜色、纹理、坐标全塞对象里：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 反面教材：每颗棋子一个对象，颜色、纹理、坐标全塞在对象里\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BadPiece\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String color;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 模拟渲染纹理：真正的内存大户（实际项目里可能是图片、字形、网格模型）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[] texture \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">256\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> *\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 1024\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">]; \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 1MB\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> x;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> y;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BadPiece\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">color\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> x\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> y\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.color \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> color;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.x \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> x;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.y \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> y;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> place\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[BadPiece] \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> color \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"棋落在 (\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> x \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \", \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> y \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \")，自带一份 1MB 纹理\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>模拟下十手棋：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">BadPiece\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> board \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ArrayList&#x3C;>();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> i \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">; i \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 10\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">; i\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">++\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    BadPiece piece \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BadPiece\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(i \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">%\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 2\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> ==\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 0\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> ?\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"黑\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> :\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"白\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, i, i \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">*\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 2\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    board.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">add\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(piece);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    piece.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">place\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"10 手棋 = \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> board.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">size\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 个对象 = 10 份 1MB 纹理\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"一盘 300 手的棋就是 300 个对象、300MB 纹理——而所有黑棋的纹理，本来就一模一样\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>===== 反面教材：每颗棋子自带一份纹理 =====\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[BadPiece] 黑棋落在 (0, 0)，自带一份 1MB 纹理\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[BadPiece] 白棋落在 (1, 2)，自带一份 1MB 纹理\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[BadPiece] 黑棋落在 (2, 4)，自带一份 1MB 纹理\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[BadPiece] 白棋落在 (3, 6)，自带一份 1MB 纹理\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[BadPiece] 黑棋落在 (4, 8)，自带一份 1MB 纹理\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[BadPiece] 白棋落在 (5, 10)，自带一份 1MB 纹理\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[BadPiece] 黑棋落在 (6, 12)，自带一份 1MB 纹理\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[BadPiece] 白棋落在 (7, 14)，自带一份 1MB 纹理\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[BadPiece] 黑棋落在 (8, 16)，自带一份 1MB 纹理\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[BadPiece] 白棋落在 (9, 18)，自带一份 1MB 纹理\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>10 手棋 = 10 个对象 = 10 份 1MB 纹理\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>一盘 300 手的棋就是 300 个对象、300MB 纹理——而所有黑棋的纹理，本来就一模一样\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>能跑，但账没法看：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>重复度触目惊心\u003C\u002Fstrong>：十个对象里其实只有两种内容——黑棋、白棋。颜色加纹理只有 2 种组合，却造了 10 个对象、10 份纹理；一盘棋 300 颗子，300MB 纹理里 298MB 是纯重复；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>真正的差异被淹没了\u003C\u002Fstrong>：十颗棋子彼此唯一不同的是坐标——几个字节的信息，却拖着 1MB 的车身；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>对象数随对局线性膨胀\u003C\u002Fstrong>：每落一子堆一个对象，一场多桌并发的棋牌室，内存曲线直接跟着在线人数起飞。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>病根只有一个：\u003Cstrong>\"不变的 1MB\"和\"多变的几个字节\"被塞进了同一个对象，重复的部分跟着实例数一起翻倍\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Ch3 id=\"toc-8\">2. 享元版：一份纹理，全盘共享\u003C\u002Fh3>\n\u003Cp>先动刀切状态——纹理和颜色留下（内部状态），坐标赶出去（外部状态）：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 享元：颜色和纹理是内部状态（不变、全盘共享），坐标是外部状态（用时传入）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> GoPiece\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String color;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 纹理只需两份：黑一份、白一份，全盘所有同色棋子共享这同一份\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[] texture \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">256\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> *\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 1024\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">]; \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 1MB\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> GoPiece\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">color\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.color \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> color;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 坐标 x、y 是外部状态：每次落子由调用方传入，享元对象自己不保存\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> place\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> x\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> y\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[GoPiece] \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> color \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"棋落在 (\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> x \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \", \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> y \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \")，纹理共享全盘同一份\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>注意两个细节：字段全是 final——\u003Cstrong>享元被全盘共享，必须不可变\u003C\u002Fstrong>，哪个调用方手贱改了纹理，全盘棋子跟着变脸；place 的参数 x、y——\u003Cstrong>每颗棋子落哪由调用方告诉它\u003C\u002Fstrong>，对象自己\"失忆\"了。\u003C\u002Fp>\n\u003Cp>再配工厂，保证\"同一颜色全 JVM 一份\"：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 享元工厂：按内部状态（颜色）缓存享元，保证每种颜色全 JVM 只有一份\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PieceFactory\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Map&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">String\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">GoPiece\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> CACHE \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> HashMap&#x3C;>();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> GoPiece \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">color\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        GoPiece piece \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> CACHE.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(color);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (piece \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">==\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> null\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            piece \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> GoPiece\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(color);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            CACHE.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">put\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(color, piece);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[工厂] 第一次要\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> color \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"棋，创建并缓存（此后全盘复用）\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> piece;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>同样的四手棋：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 黑白各只有一份，坐标作为外部状态在调用时传入\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">PieceFactory.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"黑\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">place\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">3\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">3\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">PieceFactory.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"白\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">place\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">15\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">15\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">PieceFactory.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"黑\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">place\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">16\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">3\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">PieceFactory.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"白\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">place\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">2\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">2\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">GoPiece black1 \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> PieceFactory.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"黑\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">GoPiece black2 \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> PieceFactory.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">get\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"黑\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"两次取到的黑棋是同一个对象吗？\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (black1 \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">==\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> black2));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"全盘 300 手也一样：对象数永远 2 个，纹理永远 2MB\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>===== 正解：享元模式 =====\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[工厂] 第一次要黑棋，创建并缓存（此后全盘复用）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[GoPiece] 黑棋落在 (3, 3)，纹理共享全盘同一份\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[工厂] 第一次要白棋，创建并缓存（此后全盘复用）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[GoPiece] 白棋落在 (15, 15)，纹理共享全盘同一份\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[GoPiece] 黑棋落在 (16, 3)，纹理共享全盘同一份\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[GoPiece] 白棋落在 (2, 2)，纹理共享全盘同一份\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>两次取到的黑棋是同一个对象吗？true\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>全盘 300 手也一样：对象数永远 2 个，纹理永远 2MB\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>对照反面教材逐条验收：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>对象数与落子数\u003Cstrong>彻底脱钩\u003C\u002Fstrong>：反面教材 300 手 300 个对象，正解 300 手还是 2 个——内存从 O(手数) 降到 O(颜色数)，这正是定义里\"支持大量细粒度对象\"的实现方式：大量对象被\"一份享元 + 每次调用的外部状态\"撑起来了；\u003C\u002Fli>\n \u003Cli>\u003Ccode>black1 == black2\u003C\u002Fcode> 为 true——工厂真的只发同一份对象，连\"==\"都验明正身；\u003C\u002Fli>\n \u003Cli>谁该记住坐标？\u003Cstrong>棋盘\u003C\u002Fstrong>。真实实现里落子记录是棋盘上的一个 \u003Ccode>List&lt;Placement&gt;\u003C\u002Fcode>（每条只存颜色引用 + 坐标），棋盘管外部状态、享元管内部状态，职责各归其位；\u003C\u002Fli>\n \u003Cli>纹理这种重资源，从\"随对象生\"变成\"随类别生\"——加载一次，全盘受益，连加载耗时都省了。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"toc-9\">五、Integer 缓存与字符串常量池：JDK 里的享元\u003C\u002Fh2>\n\u003Cp>理论讲完，去看这套机制在 JDK 里的样子——先看一段让无数人怀疑人生的代码：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">Integer a \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 127\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">Integer b \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 127\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">Integer c \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 128\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">Integer d \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 128\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(a \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">==\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> b);   \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F true\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(c \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">==\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> d);   \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F false\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>同一个写法，127 相等、128 不等，玄机全在 Integer.valueOf 的源码里：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Integer \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">valueOf\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> i) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    if\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (i \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">>=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> IntegerCache.low \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x26;&#x26;\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> i \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">&#x3C;=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> IntegerCache.high)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> IntegerCache.cache[i \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">-\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">IntegerCache.low)];  \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 命中缓存，返回共享对象\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    return\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Integer\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(i);                                    \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 超范围，每次真 new\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>IntegerCache 是个静态内部类，类加载时就把 -128 到 127 这 256 个 Integer \u003Cstrong>提前 new 好、放进数组\u003C\u002Fstrong>——一个提前造好的享元池，PieceFactory 需要时才创建，IntegerCache 是\"先造好等你来取\"，思路一脉相承。\u003Ccode>Integer a = 127\u003C\u002Fcode> 的自动装箱编译成 \u003Ccode>Integer.valueOf(127)\u003C\u002Fcode>，命中缓存，a 和 b 拿到的是\u003Cstrong>同一个对象\u003C\u002Fstrong>，所以 == 为 true；128 超出范围，c 和 d 各自 new，== 自然 false。\u003C\u002Fp>\n\u003Cp>这就是各大编码规范反复强调\"包装类比较必须用 equals\"的技术根源——\u003Cstrong>享元缓存的存在，让 == 的结果跟数值范围挂钩\u003C\u002Fstrong>，这种语义陷阱只能靠规范绕开。顺带两个冷知识：缓存上限可以用 \u003Ccode>-XX:AutoBoxCacheMax=&lt;size&gt;\u003C\u002Fcode> 调大（下限 -128 动不了）；Long、Short、Byte、Character 也有各自的缓存，只是范围写死、不能调。\u003C\u002Fp>\n\u003Cp>字符串那边是另一副景象。\u003Ccode>String s = \"hello\"\u003C\u002Fcode> 的字面量进\u003Cstrong>字符串常量池\u003C\u002Fstrong>，两个类里写同一个字面量，指向池里同一份：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">String s1 \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"享元\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">String s2 \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"享元\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(s1 \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">==\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> s2);          \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F true：常量池里就一份\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(s1 \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">==\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> String\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"享元\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">)); \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F false：new 出来的不走池\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(s1 \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">==\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> String\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"享元\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">).\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">intern\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">()); \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F true：intern() 把它送回池里对齐\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>和 IntegerCache 比，常量池是\"运行时也能进进出出的享元池\"——intern() 就是手动登记处：池里有就返回池里那份，没有就登记当前这份。字符串不可变（String 全字段 final），恰好满足享元\"共享必须不可变\"的铁律——JDK 这两套享元，一套提前造、一套随时进，把\"对象复用\"做进了语言的地基。\u003C\u002Fp>\n\u003Ch2 id=\"toc-10\">写在最后\u003C\u002Fh2>\n\u003Cp>回顾这一篇：享元模式把对象劈成两半——内部状态（不变、共享）留在对象里，外部状态（多变）赶出去做参数，配一个工厂保证\"同一状态全 JVM 一份\"，海量对象瘦身成有限几个；反面教材\"一颗棋子一份纹理\"300MB 的账单，享元两行字抹平；包装类缓存、字符串常量池、枚举、POI 的 CellStyle、棋牌服务端的牌对象，全是它的地盘；享元 vs 对象池的分水岭是\"并发共享的不可变对象\"还是\"轮流独占的可变对象\"——连接池线程池都不算享元。\u003C\u002Fp>\n\u003Cp>下一篇 ⑬ 代理模式，结构型最后一位压轴出场。它的 UML 和装饰器几乎一个模子：同样实现同一个接口、同样手里攥着一个同类对象——区别全在意图上：装饰器回答\"怎么给它加功能\"，代理回答\"怎么替它管住访问\"。懒加载、权限校验、远程调用，还有 JDK 动态代理、CGLIB、Spring AOP 这整座大厦的地基，全是这一篇的内容。\u003C\u002Fp>",[113,116,119,123,126,129,132,135,138,141],{"id":114,"text":115,"level":108},"toc-1","一、什么是享元模式？",{"id":117,"text":118,"level":108},"toc-2","二、享元模式的优缺点",{"id":120,"text":121,"level":122},"toc-3","优点",3,{"id":124,"text":125,"level":122},"toc-4","缺点",{"id":127,"text":128,"level":108},"toc-5","三、享元模式的应用场景",{"id":130,"text":131,"level":108},"toc-6","四、享元模式的实现方式",{"id":133,"text":134,"level":122},"toc-7","1. 反面教材：每颗棋子一份纹理",{"id":136,"text":137,"level":122},"toc-8","2. 享元版：一份纹理，全盘共享",{"id":139,"text":140,"level":108},"toc-9","五、Integer 缓存与字符串常量池：JDK 里的享元",{"id":142,"text":143,"level":108},"toc-10","写在最后"]