• 设计模式 笔记9 | 原型模式 在源码中的应用 | ArrayList 中的原型模式 | clone浅克隆 | 基于二进制流的深克隆实现


    一、原型模式 知识回顾


    原型模式定义: 用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。
    优点;

    • Java 的原型模式是基于内存二进制流数据的复制,在性能上比 new 一个新对象更高
    • 深克隆可保存对象的状态,简化了创建对象的过程

    缺点:

    • 需要为每一个类都配置 clone 方法
    • clone 方法位于类的内部,当对已知的类修改时,需修改代码,违背了开闭原则

    主要组成:

    • 抽象原型类,规定了具体原型对象必须实现的接口
    • 具体原型类,实现抽象原型类的 clone 方法,是可被复制的对象
    • 访问类,使用具体原型类中的 clone 方法来复制新的对象

    分类: 浅克隆 和 深克隆 两种

    • 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,但是对于非基本类型的属性(比如int、float、User 等),仍指向原有属性指向的对象的内存地址
    • 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原来的对象地址

    Java 中的 Object 类提供了浅克隆到 clone 方法,具体原型类只要实现 cloneable 接口就可以实现对象的浅克隆。

    模板代码:

    浅拷贝:直接用等号赋值

    public class Demo{
        public static void main(String[] args) {
            Object a = new Object();
            Object b = a;   // 浅克隆
            System.out.println(a == b);	//  输出结果为 true
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    深拷贝:实现 Cloneable 接口,重写 clone 方法,对于非基本类型的属性需要重新new对象,
    Java 自带的 clone 方法实际上是一种浅克隆,只会克隆外层的对象,其内部的对象还是需要手动 new 的

    class User implements Cloneable{
        String name;
        public User(String name) {this.name = name;}
    
        public String getName() {
            return name;
        }
    
        @Override
        protected User clone() throws CloneNotSupportedException {
            User user = (User) super.clone();
            user.name = new String(name);
            return user;
        }
    }
    public class Demo{
    
        public static void main(String[] args) throws CloneNotSupportedException {
            User uni1 = new User("uni");
            User uni2 = uni1.clone();
            System.out.println(uni1 == uni2);	 // false
            System.out.println(uni1.getName() == uni2.getName());	// false
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    应用场景:

    • 对象之间相同或者相似,只有部分几个属性不同
    • 创建对象的成本较大,比如初始化时间长,占用 CPU 太多,或占用网络资源太多等
    • 创建一个对象需要繁琐的数据准备或访问权限等,需提高性能或提高安全性

    二、深克隆 与 浅克隆


    在 JDK 8 源码中, Clonable 是一个从JDK1.0 出现的没有任何声明方法的空接口,源码如下:

    package java.lang;
    public interface Cloneable {
    }
    
    
    • 1
    • 2
    • 3
    • 4

    Java 提供 Cloneable 接口的作用:在运行时通知 JVM 可以安全地在该类上使用 clone () 方法,而如果该类没有实现 Clonebale 接口,那么调用 clone 方法则会抛出 CloneNotSupportedException 异常

    使用 clone 方法需满足的条件:

    1. 【必选】克隆对象与原型对象不是同一个对象,即对任何对象 o,都有 o.clone() != o
    2. 【必选】克隆对象与原型对象的类型一样,即对任何对象 o, 都有 o.clone().getClass() == o.getClass()
    3. 【可选】对象 o 的 equals () 方法应当满足 o.clone().equals(o) == true 恒成立

    基于之前的例子,我们定义了支持深克隆的 User对象,代码如下:

    class User implements Cloneable{
        String name;
        public User(String name) {this.name = name;}
    
        public String getName() {
            return name;
        }
    
        @Override
        protected User clone() throws CloneNotSupportedException {
            User user = (User) super.clone();
            user.name = new String(name);
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    其中 super.clone() 方法直接从堆内存中以二进制流的方式进行复制,重新分配一个内存块,因此效率很高,由于是基于内存复制的,故不会调用对象的构造方法,即不会经历初始化。

    Java 自带的 clone 方法属于浅克隆,对于 User 对象里的引用类型 String ,在克隆时仍需要手动进行 new,而在 Java 中,要想真正实现深克隆,通常会使用 序列化(Serializable)的方式,即需要实现 Serializable 接口,比如:

    import java.io.*;
    
    class User implements Cloneable, Serializable {
        String name;
        public User(String name) {this.name = name;}
    
        public String getName() {
            return name;
        }
    
        @Override
        protected User clone() throws CloneNotSupportedException {
            try{
                // 1. 创建字节数组输出流
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                // 2. 创建对象输出流
                ObjectOutputStream oos = new ObjectOutputStream(bos);
                // 3. 将该对象写入输出流中
                oos.writeObject(this);
    
                // 4. 创建字节数组输入流
                ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
                // 5. 创建对象数组输入流
                ObjectInputStream ois = new ObjectInputStream(bis);
                // 6. 读取对象输入流中的对象
                return (User) ois.readObject();
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
                return null;
            }
        }
    }
    public class Demo{
    
        public static void main(String[] args) throws CloneNotSupportedException {
            User uni1 = new User("uni");
            User uni2 = uni1.clone();
            System.out.println(uni1 == uni2);
            System.out.println(uni1.getName() == uni2.getName());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    三、克隆单例对象


    如果复制的目标对象正好是单例对象,那么是否会破坏单例对象?
    例如:给单例模式的对象提供 clone 方法

    class User implements Cloneable{
        //  单例模式: 饿汉式
        private static User instance = new User();
        // 私有化无参构造
        private User() {}
        // 提供获取实例的方法
        public static User getInstance() { return instance; }
        // 克隆
        @Override
        protected User clone() {
            try {
                return (User) super.clone();
            } catch (CloneNotSupportedException e) {
                return null;
            }
        }
    }
    public class Demo{
        public static void main(String[] args) {
            // 创建原型对象
            User user = User.getInstance();
            // 复制原型对象
            User cloneUser = user.clone();
            System.out.println("原型对象比较克隆对象的结果为: " + (user == cloneUser));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    运行结果
    原型对象比较克隆对象的结果为: false

    从运行结果看, clone 方法创建了两个不同的对象,这违背了单例模式的设计原则,为了防止破坏单例对象,可以采取的策略是 禁止复制,接下来有两种方式
    方式一:【推荐】不实现 Cloneable 接口,这样对象就无法克隆
    方式二:实现 Cloneable 接口,重写 clone 方法,直接返回单例对象

    protected User clone() {
        return instance;
    }
    
    • 1
    • 2
    • 3

    四、ArrayList 类中的原型模式


    通过之前的学习回顾,原型模式分为浅克隆和深克隆,而深克隆通常要实现 Cloneable 接口,并重写 clone() 方法,所以我们只需在源码中查看哪些类实现了 Clonebale方法,如果实现了说明这个类很有可能就是支持深克隆的原型模式,接下来我们从 JDK 中运用比较多的 ArrayList 开始了解原型模式。

    package java.util;
    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
       public Object clone() {
            try {
                // 1. 直接克隆整个列表
                ArrayList<?> v = (ArrayList<?>) super.clone();
                // 2. 调用 Ararys.copyof 克隆列表的每个元素
                v.elementData = Arrays.copyOf(elementData, size);
                // 3. 初始化modCount, 表示列表在结构上被修改的次数
                v.modCount = 0;
                return v;
            } catch (CloneNotSupportedException e) {
                // this shouldn't happen, since we are Cloneable
                throw new InternalError(e);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    java.util.ArrayList源码中的 clone 方法可以看出

    import java.util.ArrayList;
    
    class User{
        private String name;
        public User(String name) {this.name = name; }
        public void setName(String name) {this.name = name;} 
        @Override
        public String toString() {
            return "User{name='" + name + '\'' + '}';
        }
    }
    public class Demo{
        public static void main(String[] args) {
            ArrayList<User> students = new ArrayList<>();
            for (int i = 1; i <= 3; i++) {
                students.add(new User("user" + i));
            }
            ArrayList<User> cloneStudents = (ArrayList<User>) students.clone();
            System.out.println(students);
            System.out.println(cloneStudents);
            System.out.println("克隆列表与原型列表对比: " + (students == cloneStudents));
            for (int i = 0; i < students.size(); i++) {
                System.out.println(
                        "克隆列表的元素与原型列表元素的对比: [" + i + "]" +
                        (students.get(i) == students.get(i)));
    
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    运行结果
    [User{name=‘user1’}, User{name=‘user2’}, User{name=‘user3’}]
    [User{name=‘user1’}, User{name=‘user2’}, User{name=‘user3’}]
    克隆列表与原型列表对比: false
    克隆列表的元素与原型列表元素的对比: [0]true
    克隆列表的元素与原型列表元素的对比: [1]true
    克隆列表的元素与原型列表元素的对比: [2]true

    这里可以看到,因为 ArrayList 存储的对象 User 没有实现 Cloneable 接口,调用 ArrayList 的 clone() 方法,最后输出的结果虽然是不同的 ArrayList 对象,但是其内部的 User 对象仍然是相等的,即没有实现真正的深克隆。
    接下来,修改克隆后列表里的第一项,然后输出原型列表

    public class Demo{
        public static void main(String[] args) {
            ArrayList<User> students = new ArrayList<>();
            for (int i = 1; i <= 3; i++) {
                students.add(new User("user" + i));
            }
            ArrayList<User> cloneStudents = (ArrayList<User>) students.clone(); // 克隆列表
            User user = cloneStudents.get(0);                                   // 获取克隆列表里的用户对象
            user.setName("update user.");                                       // 修改用户名称
            students.forEach(System.out::println);                              // 输出原型用户列表
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    运行结果
    User{name=‘update user.’}
    User{name=‘user2’}
    User{name=‘user3’}

    结果显示,原型列表里的User对象被修改了,说明之前就是浅拷贝到形式,虽然两个列表指向的地址不同,但是其内部的 User 对象所指向的地址仍然是同一个。

    最简单的解决办法就是提供一个基于字节流的深克隆方法:

    /*
        List 内所有元素必须实现 Serializable 接口,否则在序列化时候会被报错
    */
    public static List clone(List src){
        try{
            // 拷贝
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(src);
            // 保存
            ByteArrayInputStream bai = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bai);
            return (List) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            // 不处理
        }
        return null;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    五、总结


    原型模式在源码中应用广泛,实现了 Cloneable 接口的类一般是与克隆相关的,原型模式中的原型是指克隆时参考的对象,克隆分为浅克隆和深克隆,Object类提供的clone 方法属于浅克隆,实现深克隆的最简单方式就是基于字节流进行操作,一共分为六步,进行深克隆需保证里面的非基本类型都实现了 Serializable 接口,即支持可序列化:

    // 1. 创建字节数组输出流
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    // 2. 转化为对象输出流
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    // 3. 将深克隆的对象写入到对象输出流
    oos.writeObject(src);
    // 4. 将 字节数组输出流 读取为 字节数组输入流
    ByteArrayInputStream bai = new ByteArrayInputStream(bos.toByteArray());
    // 5. 转化为对象输入流
    ObjectInputStream ois = new ObjectInputStream(bai);
    // 6. 读取对象
    return (List) ois.readObject();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    参考资料


    [1] http://c.biancheng.net/view/1343.html

  • 相关阅读:
    OC中转化成Model 和 普通的NString获取值的不同
    中南大学2021级云计算复习笔记
    LeNet学习CIFAR-10数据集识别图片类别
    C# 快速排序
    Tidb简介与应用实践
    coredns无法启动
    台式电脑怎么无损备份迁移系统到新硬盘(使用傲梅,免费的就可以)
    monitor link 联合smart link配合应对复杂的网络
    【好书推荐】计算机考研精炼1000题——考研408不可或缺
    实战:基于卷积的MNIST手写体分类
  • 原文地址:https://blog.csdn.net/Unirithe/article/details/126026919