[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site-settings":3,"\u002Farticles\u002Fdesign-pattern-05":26,"\u002Farticles\u002Fdesign-pattern-05\u002Frelated":43,"\u002Farticles\u002Fdesign-pattern-05\u002Fadjacent":72,"\u002Fcategories":83,"render-design-pattern-05":109},{"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},"2113931247385317377","design-pattern-05","设计模式修炼之路 ⑤ 原型模式——clone() 的浅与深","clone() 与 Cloneable、浅拷贝踩坑、逐字段克隆与序列化两种深拷贝","2113897628365230081",1,2661,5758,15,"2023-11-17T17:12:14","上一篇《设计模式修炼之路-建造者模式》里，我们用链式 Builder 优雅地造出了披萨和套餐。至此，\"造对象\"的姿势已经见过三种：单例管\"造几个\"，工厂管\"从哪造\"，建造者管\"怎么装\"。创建型模式还剩最后一个问题没人管——**能不能干脆别造了，直接抄一份？**\n\n造一只\"羊\"如果很便宜，new 一下无所谓；可要是这只羊的构造函数要查三次数据库、算两轮报表、读一堆配置，而你需要的不过是一只和手里这只几乎一模一样的羊呢？这时候最经济的办法，就是原型模式的主张：**找个现成的，复制一份**。说好的克隆羊多利，这就来。\n\n## 一、什么是原型模式？\n\n原型模式（Prototype Pattern）属于创建型模式，GoF 23 种之一。先看定义：\n\n> 用原型实例指定创建对象的种类，并通过**拷贝**这些原型来创建新的对象。\n\n翻译成人话：**不走 new 的路子，拿一个现成对象当\"母版\"，复制出新的来**。\n\n克隆羊多利就是现成的类比：多利不是公羊母羊\"构造\"出来的，而是从一只白面母羊的体细胞里取出细胞核、移植培育而成——遗传信息和\"母版\"完全一致。母版羊就是原型（Prototype），多利就是那次 clone() 的返回值。\n\nJava 里实现克隆是一套\"三件套\"：\n\n1. **Object.clone()**：所有类的祖宗 Object 自带一个 protected 的 native 方法 clone()，负责把对象逐字段复制一份；\n2. **Cloneable 接口**：一个方法都没有的\"标记接口\"（marker interface），唯一作用是给 clone() 发授权——类不实现它，super.clone() 在运行时直接抛 CloneNotSupportedException；\n3. **重写 clone()**：把访问权限改成 public，让外部调得到。\n\n角色也只有两个，是五个创建型模式里最简的：\n\n- **Prototype（抽象原型）**：声明克隆能力。在 Java 里就是 Cloneable 接口加各自重写的 clone() 方法；\n- **ConcretePrototype（具体原型）**：实现克隆逻辑的具体类。\n\n进阶玩法还有\"原型注册表\"（Prototype Registry）：把常用母版缓存进一个 Map，按 key 取出来克隆——游戏里的怪物模板、编辑器里的样式库都是这个思路，本篇不展开。\n\n而原型模式真正的灵魂、也是面试的重灾区，是**浅拷贝与深拷贝**的区别。不急，第四节用实验现场复现这个坑。\n\n## 二、原型模式的优缺点\n\n### 优点\n\n1. **性能好**。内存逐字段复制比走一遍构造函数快得多，尤其构造里有查库、读文件这类 IO 时，差距肉眼可见。\n2. **躲开构造器的副作用**。不走 new，构造函数里的日志、计数、资源加载统统不执行——当然这也是把双刃剑，后面说。\n3. **运行时动态创建**。多态场景下你手里只握着一个对象引用，不知道（也不需要知道）它的具体类，照样能克隆出同类型的新对象；`new` 必须写死类名，做不到这一点。\n\n### 缺点\n\n1. **浅拷贝是隐形炸弹**。引用类型字段被两只对象共享，改一处、动全身，bug 隐蔽到怀疑人生。\n2. **深拷贝成本不低**。要么每个引用字段递归 clone（类多了写得吐血），要么序列化（要求整条引用链都 Serializable，还慢）。\n3. **Cloneable 机制天生别扭**。克隆不走构造函数，字段校验全部失效；《Effective Java》第 13 条专门吐槽它，标题就叫\"谨慎地覆盖 clone\"。\n\n## 三、原型模式的应用场景\n\n- **JDK 里**：`ArrayList`、`HashMap` 都实现了 Cloneable，不过它们的 clone() 都是**浅拷贝**——集合壳是新的，里面的元素还是共享的。\n- **Spring 里**：`BeanUtils.copyProperties(source, target)`，反射逐字段拷贝，DTO 转换神器，本质就是原型思想（也是浅拷贝）。\n- **游戏\u002F模板场景**：怪物按模板批量克隆、关卡按母版复制，配原型注册表食用更佳。\n- **规避重复初始化**：对象创建要查库、要 RPC、要解析模板，而新对象和现成对象只差个别字段——克隆一份改两个字段，比重新走一遍初始化省得多。\n\n**什么时候该用**：对象创建昂贵（初始化涉及 IO 或大量计算）+ 需要大量相似对象；或者运行时才知道具体类型、还必须复制它。反过来，简单的 POJO 直接 new，别拿克隆装门面。\n\n## 四、原型模式的实现方式\n\n示例代码在 design-pattern 项目的 prototype 包里：\n\n```\ndesign-pattern\u002Fsrc\u002Fmain\u002Fjava\n└── prototype                     \u002F\u002F 原型模式\n    ├── Sheep.java                \u002F\u002F 浅拷贝羊：Cloneable + clone()\n    ├── DeepSheep.java            \u002F\u002F 深拷贝羊：递归克隆 + 序列化两种方式\n    └── PrototypeTest.java        \u002F\u002F 测试入口\n```\n\n### 1. 浅拷贝：第一次克隆\n\n先造一只最普通的克隆羊，实现 Cloneable，重写 clone()。注意 friend 字段是**引用类型**，专门用它来观察拷贝的\"深浅\"：\n\n```java\npublic class Sheep implements Cloneable {\n\n    private String name;\n    private int age;\n    private Sheep friend; \u002F\u002F 引用类型字段，用来观察浅拷贝的引用共享\n\n    public Sheep(String name, int age) {\n        this.name = name;\n        this.age = age;\n    }\n\n    public Sheep getFriend() {\n        return friend;\n    }\n\n    public void setFriend(Sheep friend) {\n        this.friend = friend;\n    }\n\n    @Override\n    public Sheep clone() {\n        try {\n            return (Sheep) super.clone();\n        } catch (CloneNotSupportedException e) {\n            throw new AssertionError(e); \u002F\u002F 实现了 Cloneable 就不会走到这里\n        }\n    }\n\n    @Override\n    public String toString() {\n        return \"羊[\" + name + \", \" + age + \"岁]\";\n    }\n}\n```\n\n测试代码：给多利安排一只 2 岁的朋友汤姆，然后克隆：\n\n```java\nprivate static void shallowCopy() {\n    Sheep friend = new Sheep(\"汤姆\", 2);\n    Sheep dolly = new Sheep(\"多利\", 1);\n    dolly.setFriend(friend);\n\n    Sheep dolly2 = dolly.clone();\n    System.out.println(\"克隆出来的羊：\" + dolly2);\n    System.out.println(\"两只羊是同一个对象吗：\" + (dolly == dolly2));\n    System.out.println(\"两只羊的朋友是同一个对象吗：\" + (dolly.getFriend() == dolly2.getFriend()));\n}\n```\n\n运行结果：\n\n```\n克隆出来的羊：羊[多利, 1岁]\n两只羊是同一个对象吗：false\n两只羊的朋友是同一个对象吗：true\n```\n\n前两行看着皆大欢喜：克隆成功，而且 `dolly == dolly2` 是 false，确实是新对象。但第三行翻车了——**两只羊的 friend 存的是同一个地址**，它们牵着同一只汤姆。\n\n这就是 super.clone() 的行为：**逐字段复制，复制的是字段本身**。对 int age 这种基本类型，复制值，相安无事；对 String name 这种引用，虽然也只复制了引用，但 String 不可变，共享无害；唯独 friend 这种**可变引用类型**，复制的只是\"门牌号\"——两只羊一个壳两个门牌，推开是同一间房。\n\n这就是**浅拷贝**：壳是新的，瓤是共享的。此时对 `dolly2.getFriend()` 做任何修改，多利都同步感知，坑就这么埋下了。\n\n### 2. 深拷贝方式一：逐字段递归克隆\n\n深拷贝的思路很直白：引用指向的对象也复制一份。第一种做法是在 clone() 里对引用字段再调一次 clone()，一路递归下去：\n\n```java\npublic class DeepSheep implements Cloneable, Serializable {\n\n    private String name;\n    private int age;\n    private DeepSheep friend;\n\n    \u002F\u002F 深拷贝方式一：重写 clone()，对引用字段递归克隆\n    @Override\n    public DeepSheep clone() {\n        try {\n            DeepSheep cloned = (DeepSheep) super.clone();\n            if (friend != null) {\n                cloned.friend = friend.clone();\n            }\n            return cloned;\n        } catch (CloneNotSupportedException e) {\n            throw new AssertionError(e);\n        }\n    }\n}\n```\n\ngetter\u002Fsetter、toString 与浅拷贝版相同，略。测试代码和方式完全一样，改调 DeepSheep：\n\n```java\nprivate static void deepCopyByClone() {\n    DeepSheep friend = new DeepSheep(\"汤姆\", 2);\n    DeepSheep dolly = new DeepSheep(\"多利\", 1);\n    dolly.setFriend(friend);\n\n    DeepSheep dolly2 = dolly.clone();\n    System.out.println(\"逐字段克隆出来的羊：\" + dolly2);\n    System.out.println(\"两只羊的朋友是同一个对象吗：\" + (dolly.getFriend() == dolly2.getFriend()));\n}\n```\n\n运行结果：\n\n```\n逐字段克隆出来的羊：羊[多利, 1岁]\n两只羊的朋友是同一个对象吗：false\n```\n\n这次是 false 了——朋友也各是各的，两只羊彻底独立。代价也明摆着：**每个引用类型字段都要手工递归 clone**，字段一多、嵌套一深，写得怀疑人生；而且克隆链上任何一环没重写好 clone()，整条链当场塌方。\n\n### 3. 深拷贝方式二：序列化 + 反序列化\n\n第二种做法更\"偷懒\"：把对象连同它引用的所有对象**整棵序列化成字节数组，再反序列化回来**——对象图整体搬家，深拷贝一步到位：\n\n```java\n\u002F\u002F 深拷贝方式二：序列化 + 反序列化，整棵对象图一起复制\npublic DeepSheep deepClone() {\n    try (ByteArrayOutputStream bos = new ByteArrayOutputStream();\n         ObjectOutputStream oos = new ObjectOutputStream(bos)) {\n        oos.writeObject(this);\n        try (ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());\n             ObjectInputStream ois = new ObjectInputStream(bis)) {\n            return (DeepSheep) ois.readObject();\n        }\n    } catch (Exception e) {\n        throw new RuntimeException(e);\n    }\n}\n```\n\n注意开头 DeepSheep 的声明里除了 Cloneable 还实现了 Serializable，并声明了 serialVersionUID——**序列化这条路要求整条引用链上的类都可序列化**，少一个都走不通。测试代码：\n\n```java\nprivate static void deepCopyBySerialization() {\n    DeepSheep friend = new DeepSheep(\"汤姆\", 2);\n    DeepSheep dolly = new DeepSheep(\"多利\", 1);\n    dolly.setFriend(friend);\n\n    DeepSheep dolly2 = dolly.deepClone();\n    System.out.println(\"序列化复制出来的羊：\" + dolly2);\n    System.out.println(\"两只羊的朋友是同一个对象吗：\" + (dolly.getFriend() == dolly2.getFriend()));\n}\n```\n\n运行结果：\n\n```\n序列化复制出来的羊：羊[多利, 1岁]\n两只羊的朋友是同一个对象吗：false\n```\n\n同样是 false，同样是深拷贝。两种方式怎么选：**递归 clone 快但要手写，序列化省心但慢**（一来一回两次 IO 级操作）；字段多、嵌套深、性能不敏感，选序列化。工程里常见的\"对象转 JSON 再转回来\"就是它的近亲。\n\n顺带交代《Effective Java》的态度：它更推荐**拷贝构造器**（`new Sheep(other)`）或**拷贝工厂**——走正常构造流程，类型明确、校验有效，不踩 Cloneable 的各种坑。原型模式的思想（复制代替创建）比它的 Java 原生实现更长寿。\n\n## 五、复制对象的其他姿势\n\n实际开发里\"复制一个对象\"远不止 clone() 一条路，顺手盘一盘：\n\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>姿势\u003C\u002Fth>\u003Cth>深浅\u003C\u002Fth>\u003Cth>特点\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\u003Ctr>\u003Ctd>Object.clone()\u003C\u002Ftd>\u003Ctd>浅\u003C\u002Ftd>\u003Ctd>JDK 原生，需实现 Cloneable，Effective Java 不待见\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>拷贝构造器\u002F拷贝工厂\u003C\u002Ftd>\u003Ctd>自定义\u003C\u002Ftd>\u003Ctd>`new Sheep(other)`，类型明确，Effective Java 力荐\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>BeanUtils.copyProperties\u003C\u002Ftd>\u003Ctd>浅\u003C\u002Ftd>\u003Ctd>Spring 反射工具，字段名相同就拷，DTO 转换神器\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>序列化\u002FJSON 往返\u003C\u002Ftd>\u003Ctd>深\u003C\u002Ftd>\u003Ctd>对象图整体复制，省心但慢，要求可序列化\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\n\n选型一句话：**字段全是基本类型和 String（不可变），浅拷贝随便用；一旦有可变引用类型，老老实实深拷贝**，别让\"共享的汤姆\"混进队伍。\n\n## 写在最后\n\n回顾这一篇：原型模式不走 new，拿现成对象当母版复制，适合\"创建昂贵 + 大量相似对象\"的场景；Java 三件套是 Cloneable 接口、Object.clone()、重写 clone()；super.clone() 是浅拷贝，\"两只羊牵同一只朋友\"的坑现场复现过；深拷贝两条路，递归 clone 逐字段复制、序列化整图搬家，各有取舍。而它\"复制代替创建\"的思想早已渗透到拷贝构造器、BeanUtils、JSON 往返这些日常工具里。\n\n至此，创建型模式五篇全部完结：**单例管\"造几个\"，工厂管\"从哪造\"，建造者管\"怎么装\"，原型管\"抄一份\"**——四种姿势殊途同归，都是把对象的创建过程从业务代码里解耦出来。\n\n下一篇进入结构型模式，开篇选手：**适配器模式（Adapter）**——当你手里攥着三相插头、墙上却是两孔插座时，第一个想到的就是它。","\u003Cp>上一篇《设计模式修炼之路-建造者模式》里，我们用链式 Builder 优雅地造出了披萨和套餐。至此，\"造对象\"的姿势已经见过三种：单例管\"造几个\"，工厂管\"从哪造\"，建造者管\"怎么装\"。创建型模式还剩最后一个问题没人管——\u003Cstrong>能不能干脆别造了，直接抄一份？\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>造一只\"羊\"如果很便宜，new 一下无所谓；可要是这只羊的构造函数要查三次数据库、算两轮报表、读一堆配置，而你需要的不过是一只和手里这只几乎一模一样的羊呢？这时候最经济的办法，就是原型模式的主张：\u003Cstrong>找个现成的，复制一份\u003C\u002Fstrong>。说好的克隆羊多利，这就来。\u003C\u002Fp>\n\u003Ch2>一、什么是原型模式？\u003C\u002Fh2>\n\u003Cp>原型模式（Prototype Pattern）属于创建型模式，GoF 23 种之一。先看定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>用原型实例指定创建对象的种类，并通过\u003Cstrong>拷贝\u003C\u002Fstrong>这些原型来创建新的对象。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>不走 new 的路子，拿一个现成对象当\"母版\"，复制出新的来\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>克隆羊多利就是现成的类比：多利不是公羊母羊\"构造\"出来的，而是从一只白面母羊的体细胞里取出细胞核、移植培育而成——遗传信息和\"母版\"完全一致。母版羊就是原型（Prototype），多利就是那次 clone() 的返回值。\u003C\u002Fp>\n\u003Cp>Java 里实现克隆是一套\"三件套\"：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>Object.clone()\u003C\u002Fstrong>：所有类的祖宗 Object 自带一个 protected 的 native 方法 clone()，负责把对象逐字段复制一份；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Cloneable 接口\u003C\u002Fstrong>：一个方法都没有的\"标记接口\"（marker interface），唯一作用是给 clone() 发授权——类不实现它，super.clone() 在运行时直接抛 CloneNotSupportedException；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>重写 clone()\u003C\u002Fstrong>：把访问权限改成 public，让外部调得到。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>角色也只有两个，是五个创建型模式里最简的：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>Prototype（抽象原型）\u003C\u002Fstrong>：声明克隆能力。在 Java 里就是 Cloneable 接口加各自重写的 clone() 方法；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>ConcretePrototype（具体原型）\u003C\u002Fstrong>：实现克隆逻辑的具体类。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>进阶玩法还有\"原型注册表\"（Prototype Registry）：把常用母版缓存进一个 Map，按 key 取出来克隆——游戏里的怪物模板、编辑器里的样式库都是这个思路，本篇不展开。\u003C\u002Fp>\n\u003Cp>而原型模式真正的灵魂、也是面试的重灾区，是\u003Cstrong>浅拷贝与深拷贝\u003C\u002Fstrong>的区别。不急，第四节用实验现场复现这个坑。\u003C\u002Fp>\n\u003Ch2>二、原型模式的优缺点\u003C\u002Fh2>\n\u003Ch3>优点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>性能好\u003C\u002Fstrong>。内存逐字段复制比走一遍构造函数快得多，尤其构造里有查库、读文件这类 IO 时，差距肉眼可见。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>躲开构造器的副作用\u003C\u002Fstrong>。不走 new，构造函数里的日志、计数、资源加载统统不执行——当然这也是把双刃剑，后面说。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>运行时动态创建\u003C\u002Fstrong>。多态场景下你手里只握着一个对象引用，不知道（也不需要知道）它的具体类，照样能克隆出同类型的新对象；\u003Ccode>new\u003C\u002Fcode> 必须写死类名，做不到这一点。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>缺点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>浅拷贝是隐形炸弹\u003C\u002Fstrong>。引用类型字段被两只对象共享，改一处、动全身，bug 隐蔽到怀疑人生。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>深拷贝成本不低\u003C\u002Fstrong>。要么每个引用字段递归 clone（类多了写得吐血），要么序列化（要求整条引用链都 Serializable，还慢）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Cloneable 机制天生别扭\u003C\u002Fstrong>。克隆不走构造函数，字段校验全部失效；《Effective Java》第 13 条专门吐槽它，标题就叫\"谨慎地覆盖 clone\"。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>三、原型模式的应用场景\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>\u003Cstrong>JDK 里\u003C\u002Fstrong>：\u003Ccode>ArrayList\u003C\u002Fcode>、\u003Ccode>HashMap\u003C\u002Fcode> 都实现了 Cloneable，不过它们的 clone() 都是\u003Cstrong>浅拷贝\u003C\u002Fstrong>——集合壳是新的，里面的元素还是共享的。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Spring 里\u003C\u002Fstrong>：\u003Ccode>BeanUtils.copyProperties(source, target)\u003C\u002Fcode>，反射逐字段拷贝，DTO 转换神器，本质就是原型思想（也是浅拷贝）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>游戏\u002F模板场景\u003C\u002Fstrong>：怪物按模板批量克隆、关卡按母版复制，配原型注册表食用更佳。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>规避重复初始化\u003C\u002Fstrong>：对象创建要查库、要 RPC、要解析模板，而新对象和现成对象只差个别字段——克隆一份改两个字段，比重新走一遍初始化省得多。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Cstrong>什么时候该用\u003C\u002Fstrong>：对象创建昂贵（初始化涉及 IO 或大量计算）+ 需要大量相似对象；或者运行时才知道具体类型、还必须复制它。反过来，简单的 POJO 直接 new，别拿克隆装门面。\u003C\u002Fp>\n\u003Ch2>四、原型模式的实现方式\u003C\u002Fh2>\n\u003Cp>示例代码在 design-pattern 项目的 prototype 包里：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>design-pattern\u002Fsrc\u002Fmain\u002Fjava\n└── prototype                     \u002F\u002F 原型模式\n    ├── Sheep.java                \u002F\u002F 浅拷贝羊：Cloneable + clone()\n    ├── DeepSheep.java            \u002F\u002F 深拷贝羊：递归克隆 + 序列化两种方式\n    └── PrototypeTest.java        \u002F\u002F 测试入口\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>1. 浅拷贝：第一次克隆\u003C\u002Fh3>\n\u003Cp>先造一只最普通的克隆羊，实现 Cloneable，重写 clone()。注意 friend 字段是\u003Cstrong>引用类型\u003C\u002Fstrong>，专门用它来观察拷贝的\"深浅\"：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class Sheep implements Cloneable {\n\n    private String name;\n    private int age;\n    private Sheep friend; \u002F\u002F 引用类型字段，用来观察浅拷贝的引用共享\n\n    public Sheep(String name, int age) {\n        this.name = name;\n        this.age = age;\n    }\n\n    public Sheep getFriend() {\n        return friend;\n    }\n\n    public void setFriend(Sheep friend) {\n        this.friend = friend;\n    }\n\n    @Override\n    public Sheep clone() {\n        try {\n            return (Sheep) super.clone();\n        } catch (CloneNotSupportedException e) {\n            throw new AssertionError(e); \u002F\u002F 实现了 Cloneable 就不会走到这里\n        }\n    }\n\n    @Override\n    public String toString() {\n        return \"羊[\" + name + \", \" + age + \"岁]\";\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>测试代码：给多利安排一只 2 岁的朋友汤姆，然后克隆：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">private static void shallowCopy() {\n    Sheep friend = new Sheep(\"汤姆\", 2);\n    Sheep dolly = new Sheep(\"多利\", 1);\n    dolly.setFriend(friend);\n\n    Sheep dolly2 = dolly.clone();\n    System.out.println(\"克隆出来的羊：\" + dolly2);\n    System.out.println(\"两只羊是同一个对象吗：\" + (dolly == dolly2));\n    System.out.println(\"两只羊的朋友是同一个对象吗：\" + (dolly.getFriend() == dolly2.getFriend()));\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>克隆出来的羊：羊[多利, 1岁]\n两只羊是同一个对象吗：false\n两只羊的朋友是同一个对象吗：true\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>前两行看着皆大欢喜：克隆成功，而且 \u003Ccode>dolly == dolly2\u003C\u002Fcode> 是 false，确实是新对象。但第三行翻车了——\u003Cstrong>两只羊的 friend 存的是同一个地址\u003C\u002Fstrong>，它们牵着同一只汤姆。\u003C\u002Fp>\n\u003Cp>这就是 super.clone() 的行为：\u003Cstrong>逐字段复制，复制的是字段本身\u003C\u002Fstrong>。对 int age 这种基本类型，复制值，相安无事；对 String name 这种引用，虽然也只复制了引用，但 String 不可变，共享无害；唯独 friend 这种\u003Cstrong>可变引用类型\u003C\u002Fstrong>，复制的只是\"门牌号\"——两只羊一个壳两个门牌，推开是同一间房。\u003C\u002Fp>\n\u003Cp>这就是\u003Cstrong>浅拷贝\u003C\u002Fstrong>：壳是新的，瓤是共享的。此时对 \u003Ccode>dolly2.getFriend()\u003C\u002Fcode> 做任何修改，多利都同步感知，坑就这么埋下了。\u003C\u002Fp>\n\u003Ch3>2. 深拷贝方式一：逐字段递归克隆\u003C\u002Fh3>\n\u003Cp>深拷贝的思路很直白：引用指向的对象也复制一份。第一种做法是在 clone() 里对引用字段再调一次 clone()，一路递归下去：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class DeepSheep implements Cloneable, Serializable {\n\n    private String name;\n    private int age;\n    private DeepSheep friend;\n\n    \u002F\u002F 深拷贝方式一：重写 clone()，对引用字段递归克隆\n    @Override\n    public DeepSheep clone() {\n        try {\n            DeepSheep cloned = (DeepSheep) super.clone();\n            if (friend != null) {\n                cloned.friend = friend.clone();\n            }\n            return cloned;\n        } catch (CloneNotSupportedException e) {\n            throw new AssertionError(e);\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>getter\u002Fsetter、toString 与浅拷贝版相同，略。测试代码和方式完全一样，改调 DeepSheep：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">private static void deepCopyByClone() {\n    DeepSheep friend = new DeepSheep(\"汤姆\", 2);\n    DeepSheep dolly = new DeepSheep(\"多利\", 1);\n    dolly.setFriend(friend);\n\n    DeepSheep dolly2 = dolly.clone();\n    System.out.println(\"逐字段克隆出来的羊：\" + dolly2);\n    System.out.println(\"两只羊的朋友是同一个对象吗：\" + (dolly.getFriend() == dolly2.getFriend()));\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>逐字段克隆出来的羊：羊[多利, 1岁]\n两只羊的朋友是同一个对象吗：false\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>这次是 false 了——朋友也各是各的，两只羊彻底独立。代价也明摆着：\u003Cstrong>每个引用类型字段都要手工递归 clone\u003C\u002Fstrong>，字段一多、嵌套一深，写得怀疑人生；而且克隆链上任何一环没重写好 clone()，整条链当场塌方。\u003C\u002Fp>\n\u003Ch3>3. 深拷贝方式二：序列化 + 反序列化\u003C\u002Fh3>\n\u003Cp>第二种做法更\"偷懒\"：把对象连同它引用的所有对象\u003Cstrong>整棵序列化成字节数组，再反序列化回来\u003C\u002Fstrong>——对象图整体搬家，深拷贝一步到位：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 深拷贝方式二：序列化 + 反序列化，整棵对象图一起复制\npublic DeepSheep deepClone() {\n    try (ByteArrayOutputStream bos = new ByteArrayOutputStream();\n         ObjectOutputStream oos = new ObjectOutputStream(bos)) {\n        oos.writeObject(this);\n        try (ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());\n             ObjectInputStream ois = new ObjectInputStream(bis)) {\n            return (DeepSheep) ois.readObject();\n        }\n    } catch (Exception e) {\n        throw new RuntimeException(e);\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>注意开头 DeepSheep 的声明里除了 Cloneable 还实现了 Serializable，并声明了 serialVersionUID——\u003Cstrong>序列化这条路要求整条引用链上的类都可序列化\u003C\u002Fstrong>，少一个都走不通。测试代码：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">private static void deepCopyBySerialization() {\n    DeepSheep friend = new DeepSheep(\"汤姆\", 2);\n    DeepSheep dolly = new DeepSheep(\"多利\", 1);\n    dolly.setFriend(friend);\n\n    DeepSheep dolly2 = dolly.deepClone();\n    System.out.println(\"序列化复制出来的羊：\" + dolly2);\n    System.out.println(\"两只羊的朋友是同一个对象吗：\" + (dolly.getFriend() == dolly2.getFriend()));\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>序列化复制出来的羊：羊[多利, 1岁]\n两只羊的朋友是同一个对象吗：false\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>同样是 false，同样是深拷贝。两种方式怎么选：\u003Cstrong>递归 clone 快但要手写，序列化省心但慢\u003C\u002Fstrong>（一来一回两次 IO 级操作）；字段多、嵌套深、性能不敏感，选序列化。工程里常见的\"对象转 JSON 再转回来\"就是它的近亲。\u003C\u002Fp>\n\u003Cp>顺带交代《Effective Java》的态度：它更推荐\u003Cstrong>拷贝构造器\u003C\u002Fstrong>（\u003Ccode>new Sheep(other)\u003C\u002Fcode>）或\u003Cstrong>拷贝工厂\u003C\u002Fstrong>——走正常构造流程，类型明确、校验有效，不踩 Cloneable 的各种坑。原型模式的思想（复制代替创建）比它的 Java 原生实现更长寿。\u003C\u002Fp>\n\u003Ch2>五、复制对象的其他姿势\u003C\u002Fh2>\n\u003Cp>实际开发里\"复制一个对象\"远不止 clone() 一条路，顺手盘一盘：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>姿势\u003C\u002Fth>\n   \u003Cth>深浅\u003C\u002Fth>\n   \u003Cth>特点\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>Object.clone()\u003C\u002Ftd>\n   \u003Ctd>浅\u003C\u002Ftd>\n   \u003Ctd>JDK 原生，需实现 Cloneable，Effective Java 不待见\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>拷贝构造器\u002F拷贝工厂\u003C\u002Ftd>\n   \u003Ctd>自定义\u003C\u002Ftd>\n   \u003Ctd>`new Sheep(other)`，类型明确，Effective Java 力荐\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>BeanUtils.copyProperties\u003C\u002Ftd>\n   \u003Ctd>浅\u003C\u002Ftd>\n   \u003Ctd>Spring 反射工具，字段名相同就拷，DTO 转换神器\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>序列化\u002FJSON 往返\u003C\u002Ftd>\n   \u003Ctd>深\u003C\u002Ftd>\n   \u003Ctd>对象图整体复制，省心但慢，要求可序列化\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>选型一句话：\u003Cstrong>字段全是基本类型和 String（不可变），浅拷贝随便用；一旦有可变引用类型，老老实实深拷贝\u003C\u002Fstrong>，别让\"共享的汤姆\"混进队伍。\u003C\u002Fp>\n\u003Ch2>写在最后\u003C\u002Fh2>\n\u003Cp>回顾这一篇：原型模式不走 new，拿现成对象当母版复制，适合\"创建昂贵 + 大量相似对象\"的场景；Java 三件套是 Cloneable 接口、Object.clone()、重写 clone()；super.clone() 是浅拷贝，\"两只羊牵同一只朋友\"的坑现场复现过；深拷贝两条路，递归 clone 逐字段复制、序列化整图搬家，各有取舍。而它\"复制代替创建\"的思想早已渗透到拷贝构造器、BeanUtils、JSON 往返这些日常工具里。\u003C\u002Fp>\n\u003Cp>至此，创建型模式五篇全部完结：\u003Cstrong>单例管\"造几个\"，工厂管\"从哪造\"，建造者管\"怎么装\"，原型管\"抄一份\"\u003C\u002Fstrong>——四种姿势殊途同归，都是把对象的创建过程从业务代码里解耦出来。\u003C\u002Fp>\n\u003Cp>下一篇进入结构型模式，开篇选手：\u003Cstrong>适配器模式（Adapter）\u003C\u002Fstrong>——当你手里攥着三相插头、墙上却是两孔插座时，第一个想到的就是它。\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},"2113931131085656066","design-pattern-03","设计模式修炼之路 ③ 工厂模式——简单工厂、工厂方法、抽象工厂三兄弟","简单工厂、工厂方法、抽象工厂三兄弟的演进与取舍、开闭原则得失",3741,6415,17,"2023-10-13T21:36:05",{"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},"2113931192066641922","design-pattern-04","设计模式修炼之路 ④ 建造者模式——告别重叠构造器","重叠构造器的坑、经典四角色建造者、Effective Java 式链式 Builder",3577,7441,19,"2023-11-06T18:16:44",{"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},"2113930718034792450","design-pattern-02","设计模式修炼之路 ② 单例模式——一个实例，六种写法","一个类在整个应用里只允许存在一个实例——听着是一句话需求，做起来是一路打怪：懒汉式、饿汉式、双重校验锁、静态内部类、枚举，六种写法沿着\"线程安全\"这条主线层层演进。",1708,5238,14,"2023-09-24T10:50:03",{"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":70,"publishedAt":82},"2113931305484816386","design-pattern-06","设计模式修炼之路 ⑥ 适配器模式——让不兼容的接口握手","类适配器 vs 对象适配器、在 Spring MVC 里找它",1578,5381,"2023-12-11T21:59:29",{"code":4,"message":5,"data":84},[85,91,96,102],{"id":86,"name":87,"slug":88,"description":89,"sortOrder":4,"articleCount":90},"1946000000000000001","antlr4","tech","编程与技术笔记","13",{"id":32,"name":92,"slug":93,"description":94,"sortOrder":4,"articleCount":95},"设计模式","design-pattern","设计模式系列","34",{"id":97,"name":98,"slug":99,"description":100,"sortOrder":33,"articleCount":101},"1946000000000000002","随笔","essay","生活与思考","0",{"id":103,"name":104,"slug":105,"description":106,"sortOrder":107,"articleCount":108},"2105200093764698113","工具","tools","好用的工具推荐",2,"1",{"html":110,"toc":111},"\u003Cp>上一篇《设计模式修炼之路-建造者模式》里，我们用链式 Builder 优雅地造出了披萨和套餐。至此，\"造对象\"的姿势已经见过三种：单例管\"造几个\"，工厂管\"从哪造\"，建造者管\"怎么装\"。创建型模式还剩最后一个问题没人管——\u003Cstrong>能不能干脆别造了，直接抄一份？\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>造一只\"羊\"如果很便宜，new 一下无所谓；可要是这只羊的构造函数要查三次数据库、算两轮报表、读一堆配置，而你需要的不过是一只和手里这只几乎一模一样的羊呢？这时候最经济的办法，就是原型模式的主张：\u003Cstrong>找个现成的，复制一份\u003C\u002Fstrong>。说好的克隆羊多利，这就来。\u003C\u002Fp>\n\u003Ch2 id=\"toc-1\">一、什么是原型模式？\u003C\u002Fh2>\n\u003Cp>原型模式（Prototype Pattern）属于创建型模式，GoF 23 种之一。先看定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>用原型实例指定创建对象的种类，并通过\u003Cstrong>拷贝\u003C\u002Fstrong>这些原型来创建新的对象。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>不走 new 的路子，拿一个现成对象当\"母版\"，复制出新的来\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>克隆羊多利就是现成的类比：多利不是公羊母羊\"构造\"出来的，而是从一只白面母羊的体细胞里取出细胞核、移植培育而成——遗传信息和\"母版\"完全一致。母版羊就是原型（Prototype），多利就是那次 clone() 的返回值。\u003C\u002Fp>\n\u003Cp>Java 里实现克隆是一套\"三件套\"：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>Object.clone()\u003C\u002Fstrong>：所有类的祖宗 Object 自带一个 protected 的 native 方法 clone()，负责把对象逐字段复制一份；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Cloneable 接口\u003C\u002Fstrong>：一个方法都没有的\"标记接口\"（marker interface），唯一作用是给 clone() 发授权——类不实现它，super.clone() 在运行时直接抛 CloneNotSupportedException；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>重写 clone()\u003C\u002Fstrong>：把访问权限改成 public，让外部调得到。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>角色也只有两个，是五个创建型模式里最简的：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>Prototype（抽象原型）\u003C\u002Fstrong>：声明克隆能力。在 Java 里就是 Cloneable 接口加各自重写的 clone() 方法；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>ConcretePrototype（具体原型）\u003C\u002Fstrong>：实现克隆逻辑的具体类。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>进阶玩法还有\"原型注册表\"（Prototype Registry）：把常用母版缓存进一个 Map，按 key 取出来克隆——游戏里的怪物模板、编辑器里的样式库都是这个思路，本篇不展开。\u003C\u002Fp>\n\u003Cp>而原型模式真正的灵魂、也是面试的重灾区，是\u003Cstrong>浅拷贝与深拷贝\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>。内存逐字段复制比走一遍构造函数快得多，尤其构造里有查库、读文件这类 IO 时，差距肉眼可见。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>躲开构造器的副作用\u003C\u002Fstrong>。不走 new，构造函数里的日志、计数、资源加载统统不执行——当然这也是把双刃剑，后面说。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>运行时动态创建\u003C\u002Fstrong>。多态场景下你手里只握着一个对象引用，不知道（也不需要知道）它的具体类，照样能克隆出同类型的新对象；\u003Ccode>new\u003C\u002Fcode> 必须写死类名，做不到这一点。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3 id=\"toc-4\">缺点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>浅拷贝是隐形炸弹\u003C\u002Fstrong>。引用类型字段被两只对象共享，改一处、动全身，bug 隐蔽到怀疑人生。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>深拷贝成本不低\u003C\u002Fstrong>。要么每个引用字段递归 clone（类多了写得吐血），要么序列化（要求整条引用链都 Serializable，还慢）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Cloneable 机制天生别扭\u003C\u002Fstrong>。克隆不走构造函数，字段校验全部失效；《Effective Java》第 13 条专门吐槽它，标题就叫\"谨慎地覆盖 clone\"。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-5\">三、原型模式的应用场景\u003C\u002Fh2>\n\u003Cul>\n \u003Cli>\u003Cstrong>JDK 里\u003C\u002Fstrong>：\u003Ccode>ArrayList\u003C\u002Fcode>、\u003Ccode>HashMap\u003C\u002Fcode> 都实现了 Cloneable，不过它们的 clone() 都是\u003Cstrong>浅拷贝\u003C\u002Fstrong>——集合壳是新的，里面的元素还是共享的。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Spring 里\u003C\u002Fstrong>：\u003Ccode>BeanUtils.copyProperties(source, target)\u003C\u002Fcode>，反射逐字段拷贝，DTO 转换神器，本质就是原型思想（也是浅拷贝）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>游戏\u002F模板场景\u003C\u002Fstrong>：怪物按模板批量克隆、关卡按母版复制，配原型注册表食用更佳。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>规避重复初始化\u003C\u002Fstrong>：对象创建要查库、要 RPC、要解析模板，而新对象和现成对象只差个别字段——克隆一份改两个字段，比重新走一遍初始化省得多。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Cstrong>什么时候该用\u003C\u002Fstrong>：对象创建昂贵（初始化涉及 IO 或大量计算）+ 需要大量相似对象；或者运行时才知道具体类型、还必须复制它。反过来，简单的 POJO 直接 new，别拿克隆装门面。\u003C\u002Fp>\n\u003Ch2 id=\"toc-6\">四、原型模式的实现方式\u003C\u002Fh2>\n\u003Cp>示例代码在 design-pattern 项目的 prototype 包里：\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>└── prototype                     \u002F\u002F 原型模式\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── Sheep.java                \u002F\u002F 浅拷贝羊：Cloneable + clone()\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    ├── DeepSheep.java            \u002F\u002F 深拷贝羊：递归克隆 + 序列化两种方式\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>    └── PrototypeTest.java        \u002F\u002F 测试入口\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"toc-7\">1. 浅拷贝：第一次克隆\u003C\u002Fh3>\n\u003Cp>先造一只最普通的克隆羊，实现 Cloneable，重写 clone()。注意 friend 字段是\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:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Sheep\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Cloneable\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:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String name;\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\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> age;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Sheep friend; \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 引用类型字段，用来观察浅拷贝的引用共享\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\"> Sheep\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">name\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\"> age\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\">.name \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> name;\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\">.age \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> age;\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:#24292E;--shiki-dark:#E1E4E8\"> Sheep \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getFriend\u003C\u002Fspan>\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\"> friend;\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\"> setFriend\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Sheep \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">friend\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\">.friend \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> friend;\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:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Sheep \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">clone\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        try\u003C\u002Fspan>\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\"> (Sheep) \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">super\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">clone\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>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">catch\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (CloneNotSupportedException \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">e\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            throw\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> AssertionError\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(e); \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 实现了 Cloneable 就不会走到这里\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>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">toString\u003C\u002Fspan>\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:#032F62;--shiki-dark:#9ECBFF\"> \"羊[\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> name \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\"> age \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>测试代码：给多利安排一只 2 岁的朋友汤姆，然后克隆：\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\">private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> shallowCopy\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Sheep friend \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\"> Sheep\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:#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\">    Sheep dolly \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\"> Sheep\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:#005CC5;--shiki-dark:#79B8FF\">1\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    dolly.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setFriend\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(friend);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Sheep dolly2 \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> dolly.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">clone\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\"> dolly2);\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\"> (dolly \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">==\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> dolly2));\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\"> (dolly.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getFriend\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\"> dolly2.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getFriend\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\">\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>克隆出来的羊：羊[多利, 1岁]\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>两只羊是同一个对象吗：false\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>两只羊的朋友是同一个对象吗：true\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>前两行看着皆大欢喜：克隆成功，而且 \u003Ccode>dolly == dolly2\u003C\u002Fcode> 是 false，确实是新对象。但第三行翻车了——\u003Cstrong>两只羊的 friend 存的是同一个地址\u003C\u002Fstrong>，它们牵着同一只汤姆。\u003C\u002Fp>\n\u003Cp>这就是 super.clone() 的行为：\u003Cstrong>逐字段复制，复制的是字段本身\u003C\u002Fstrong>。对 int age 这种基本类型，复制值，相安无事；对 String name 这种引用，虽然也只复制了引用，但 String 不可变，共享无害；唯独 friend 这种\u003Cstrong>可变引用类型\u003C\u002Fstrong>，复制的只是\"门牌号\"——两只羊一个壳两个门牌，推开是同一间房。\u003C\u002Fp>\n\u003Cp>这就是\u003Cstrong>浅拷贝\u003C\u002Fstrong>：壳是新的，瓤是共享的。此时对 \u003Ccode>dolly2.getFriend()\u003C\u002Fcode> 做任何修改，多利都同步感知，坑就这么埋下了。\u003C\u002Fp>\n\u003Ch3 id=\"toc-8\">2. 深拷贝方式一：逐字段递归克隆\u003C\u002Fh3>\n\u003Cp>深拷贝的思路很直白：引用指向的对象也复制一份。第一种做法是在 clone() 里对引用字段再调一次 clone()，一路递归下去：\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\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> DeepSheep\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Cloneable\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">Serializable\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:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String name;\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\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> age;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> DeepSheep friend;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 深拷贝方式一：重写 clone()，对引用字段递归克隆\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> DeepSheep \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">clone\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        try\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            DeepSheep cloned \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (DeepSheep) \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">super\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">clone\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\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\"> (friend \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\">                cloned.friend \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> friend.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">clone\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\"> cloned;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        } \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">catch\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (CloneNotSupportedException \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">e\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            throw\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> AssertionError\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(e);\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\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>getter\u002Fsetter、toString 与浅拷贝版相同，略。测试代码和方式完全一样，改调 DeepSheep：\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\">private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> deepCopyByClone\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    DeepSheep friend \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\"> DeepSheep\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:#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\">    DeepSheep dolly \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\"> DeepSheep\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:#005CC5;--shiki-dark:#79B8FF\">1\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    dolly.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setFriend\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(friend);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    DeepSheep dolly2 \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> dolly.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">clone\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\"> dolly2);\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\"> (dolly.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getFriend\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\"> dolly2.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getFriend\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\">\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>逐字段克隆出来的羊：羊[多利, 1岁]\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>两只羊的朋友是同一个对象吗：false\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>这次是 false 了——朋友也各是各的，两只羊彻底独立。代价也明摆着：\u003Cstrong>每个引用类型字段都要手工递归 clone\u003C\u002Fstrong>，字段一多、嵌套一深，写得怀疑人生；而且克隆链上任何一环没重写好 clone()，整条链当场塌方。\u003C\u002Fp>\n\u003Ch3 id=\"toc-9\">3. 深拷贝方式二：序列化 + 反序列化\u003C\u002Fh3>\n\u003Cp>第二种做法更\"偷懒\"：把对象连同它引用的所有对象\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:#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:#24292E;--shiki-dark:#E1E4E8\"> DeepSheep \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">deepClone\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    try\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (ByteArrayOutputStream bos \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\"> ByteArrayOutputStream\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">         ObjectOutputStream oos \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\"> ObjectOutputStream\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(bos)) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        oos.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">writeObject\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        try\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (ByteArrayInputStream bis \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\"> ByteArrayInputStream\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(bos.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">toByteArray\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">             ObjectInputStream ois \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\"> ObjectInputStream\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(bis)) {\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\"> (DeepSheep) ois.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">readObject\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>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">catch\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (Exception \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">e\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        throw\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RuntimeException\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(e);\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>注意开头 DeepSheep 的声明里除了 Cloneable 还实现了 Serializable，并声明了 serialVersionUID——\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:#D73A49;--shiki-dark:#F97583\">private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> deepCopyBySerialization\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    DeepSheep friend \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\"> DeepSheep\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:#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\">    DeepSheep dolly \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\"> DeepSheep\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:#005CC5;--shiki-dark:#79B8FF\">1\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    dolly.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setFriend\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(friend);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    DeepSheep dolly2 \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> dolly.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">deepClone\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\"> dolly2);\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\"> (dolly.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getFriend\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\"> dolly2.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getFriend\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\">\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>序列化复制出来的羊：羊[多利, 1岁]\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>两只羊的朋友是同一个对象吗：false\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>同样是 false，同样是深拷贝。两种方式怎么选：\u003Cstrong>递归 clone 快但要手写，序列化省心但慢\u003C\u002Fstrong>（一来一回两次 IO 级操作）；字段多、嵌套深、性能不敏感，选序列化。工程里常见的\"对象转 JSON 再转回来\"就是它的近亲。\u003C\u002Fp>\n\u003Cp>顺带交代《Effective Java》的态度：它更推荐\u003Cstrong>拷贝构造器\u003C\u002Fstrong>（\u003Ccode>new Sheep(other)\u003C\u002Fcode>）或\u003Cstrong>拷贝工厂\u003C\u002Fstrong>——走正常构造流程，类型明确、校验有效，不踩 Cloneable 的各种坑。原型模式的思想（复制代替创建）比它的 Java 原生实现更长寿。\u003C\u002Fp>\n\u003Ch2 id=\"toc-10\">五、复制对象的其他姿势\u003C\u002Fh2>\n\u003Cp>实际开发里\"复制一个对象\"远不止 clone() 一条路，顺手盘一盘：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>姿势\u003C\u002Fth>\n   \u003Cth>深浅\u003C\u002Fth>\n   \u003Cth>特点\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>Object.clone()\u003C\u002Ftd>\n   \u003Ctd>浅\u003C\u002Ftd>\n   \u003Ctd>JDK 原生，需实现 Cloneable，Effective Java 不待见\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>拷贝构造器\u002F拷贝工厂\u003C\u002Ftd>\n   \u003Ctd>自定义\u003C\u002Ftd>\n   \u003Ctd>`new Sheep(other)`，类型明确，Effective Java 力荐\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>BeanUtils.copyProperties\u003C\u002Ftd>\n   \u003Ctd>浅\u003C\u002Ftd>\n   \u003Ctd>Spring 反射工具，字段名相同就拷，DTO 转换神器\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>序列化\u002FJSON 往返\u003C\u002Ftd>\n   \u003Ctd>深\u003C\u002Ftd>\n   \u003Ctd>对象图整体复制，省心但慢，要求可序列化\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>选型一句话：\u003Cstrong>字段全是基本类型和 String（不可变），浅拷贝随便用；一旦有可变引用类型，老老实实深拷贝\u003C\u002Fstrong>，别让\"共享的汤姆\"混进队伍。\u003C\u002Fp>\n\u003Ch2 id=\"toc-11\">写在最后\u003C\u002Fh2>\n\u003Cp>回顾这一篇：原型模式不走 new，拿现成对象当母版复制，适合\"创建昂贵 + 大量相似对象\"的场景；Java 三件套是 Cloneable 接口、Object.clone()、重写 clone()；super.clone() 是浅拷贝，\"两只羊牵同一只朋友\"的坑现场复现过；深拷贝两条路，递归 clone 逐字段复制、序列化整图搬家，各有取舍。而它\"复制代替创建\"的思想早已渗透到拷贝构造器、BeanUtils、JSON 往返这些日常工具里。\u003C\u002Fp>\n\u003Cp>至此，创建型模式五篇全部完结：\u003Cstrong>单例管\"造几个\"，工厂管\"从哪造\"，建造者管\"怎么装\"，原型管\"抄一份\"\u003C\u002Fstrong>——四种姿势殊途同归，都是把对象的创建过程从业务代码里解耦出来。\u003C\u002Fp>\n\u003Cp>下一篇进入结构型模式，开篇选手：\u003Cstrong>适配器模式（Adapter）\u003C\u002Fstrong>——当你手里攥着三相插头、墙上却是两孔插座时，第一个想到的就是它。\u003C\u002Fp>",[112,115,118,122,125,128,131,134,137,140,143],{"id":113,"text":114,"level":107},"toc-1","一、什么是原型模式？",{"id":116,"text":117,"level":107},"toc-2","二、原型模式的优缺点",{"id":119,"text":120,"level":121},"toc-3","优点",3,{"id":123,"text":124,"level":121},"toc-4","缺点",{"id":126,"text":127,"level":107},"toc-5","三、原型模式的应用场景",{"id":129,"text":130,"level":107},"toc-6","四、原型模式的实现方式",{"id":132,"text":133,"level":121},"toc-7","1. 浅拷贝：第一次克隆",{"id":135,"text":136,"level":121},"toc-8","2. 深拷贝方式一：逐字段递归克隆",{"id":138,"text":139,"level":121},"toc-9","3. 深拷贝方式二：序列化 + 反序列化",{"id":141,"text":142,"level":107},"toc-10","五、复制对象的其他姿势",{"id":144,"text":145,"level":107},"toc-11","写在最后"]