原型模式主要在于对象的克隆,所以也叫克隆模式

其实就是利用java中的Object对象中的clone方法实现一个对象的克隆。此方法需要注意的是,一个对象想要实现克隆,就必须实现一个标志性接口Cloneable

现在先来说一下浅克隆

这玩意也叫表皮克隆,简单点就是说里面的引用的引用对象没有进行实质性的克隆,而是指向一片共同空间

话不多说,直接上代码

ConcretePrototype.java

package pxx;



import java.util.List;

public class ConcretePrototype implements Cloneable {

    private int age;
    private String name;
    private List<String> hobbies;

    @Override
    public ConcretePrototype clone() {
        try {
            return (ConcretePrototype)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

    @Override
    public String toString() {
        return "ConcretePrototype{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", hobbies=" + hobbies +
                '}';
    }
}

 下面去看客户端Client.java

package pxx;

import java.util.ArrayList;
import java.util.List;

public class Client {


    public static void main(String[] args) {
        //创建原型对象
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAge(18);
        prototype.setName("Tom");
        List<String> hobbies = new ArrayList<String>();
        hobbies.add("书法");
        hobbies.add("美术");
        prototype.setHobbies(hobbies);

        //拷贝原型对象
        ConcretePrototype cloneType = prototype.clone();
        cloneType.setAge(520);
        cloneType.getHobbies().add("技术控");


        System.out.println("原型对象:" + prototype);
        System.out.println("克隆对象:" + cloneType);
        //这里返回的是false
        //对象的地址肯定是不一样的
        System.out.println(prototype == cloneType);


        System.out.println("原型对象的爱好:" + prototype.getHobbies());
        System.out.println("克隆对象的爱好:" + cloneType.getHobbies());    
        //这里是true,浅克隆的时候引用对象是指向同一片空间的
        System.out.println(prototype.getHobbies() == cloneType.getHobbies());//地址一样

    }

}

直接上一下打印结果

 

下面在来上一下浅拷贝的具体分析

 上面就很明显看到复制过去一个对象之后,就相当于在堆上面开了一个新空间,你做出相应的改值之后,这个只是对于普通类型来说,是对原对象互不影响的

所以上面prototype == cloneType这个进行判断的时候,会出现false,因为这两者的内存地址本来就不一样。这也就是为什么prototype.getHobbies() == cloneType.getHobbies() 的时候为什么返回true的原因,因为他们指向的内存地址都一样。

值得注意的就是,普通的基本数据类型,是各自占据自己的空间,数据修改互不影响。

对于浅拷贝存在的一个误区

假设我们引用一个数组,其实只要是引用对象都可以,用数组只是为了方便观察,直接上代码,全都放在代码里面

package com.pxx.practice.jianzhi.test.copy;

import java.util.Arrays;

public class ShallowDemo1 implements Cloneable{

    private int age;
    private int[] arr;//引用对象数组


    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int[] getArr() {
        return arr;
    }

    public void setArr(int[] arr) {
        this.arr = arr;
    }

    @Override
    public String toString() {
        return "ShallowDemo1{" +
                "age=" + age +
                ", arr=" + Arrays.toString(arr) +
                '}';
    }
}

class Client1 {
    public static void main(String[] args) throws CloneNotSupportedException {
        ShallowDemo1 sd1 = new ShallowDemo1();
        sd1.setAge(111);
        int[] arr1 = {1, 2, 3};
        sd1.setArr(arr1);
        System.out.println(sd1);//123

        //开始浅拷贝一把
        //这里浅拷贝,我必须进行一个说明
        //对于引用对象来讲,当前该引用对象如果在堆上面开辟了一个空间
        //那么拷贝之后的引用对象一定会指向之前引用对象在堆上开辟的空间
        //但是你要明白,每一个指向这个堆空间的变量,都是分别在两个独立的空间栈
        //换句话说,改变其中一个空间栈里面变量的指向,并不会影响另外一个的指向
        ShallowDemo1 sd2 = (ShallowDemo1)sd1.clone();
        System.out.println(sd2);//123

        //改变数组中其中某一个数据,两个就都改变了
        int[] parr = sd2.getArr();//获取sd1与sd2指向同一片空间的引用地址
        parr[0] = 321;

        //这里两者都会改变,因为他们是指向了同一片空间
        System.out.println(sd1);//321 2 3
        System.out.println(sd2);//321 2 3

        //这里只是改变sd2栈上arr2的变量指向,没有改动sd1,所以sd1是不动的
        int[] arr2 = {2, 2, 2};
        sd2.setArr(arr2);
        //数组类型不会都到影响
        System.out.println(sd1);//321,2,3
        System.out.println(sd2);//2,2,2
    }
}

深拷贝

下面我们进入深克隆,解决prototype.getHobbies() == cloneType.getHobbies()内存地址指向同一个堆地址的问题

 

话不多说,直接上代码

ConcretePrototype.java

package pxx;



import java.util.ArrayList;
import java.util.List;

public class ConcretePrototype implements Cloneable {

    private int age;
    private String name;
    private List<String> hobbies;

    @Override
    public ConcretePrototype clone() {
        try {
            //无非就是把hobbies也进行一个克隆
            //ArrayList集合对象本身就是实现了Cloneable接口的
            //所以ArrayList可以直接实现克隆方法
            //但是List接口是没有去实现这个Cloneable接口的
            ConcretePrototype result = (ConcretePrototype)super.clone();
            //把这个对象内部的集合对象也做一个克隆
            result.hobbies = (List)((ArrayList)result.getHobbies()).clone();
            return result;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

    @Override
    public String toString() {
        return "ConcretePrototype{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", hobbies=" + hobbies +
                '}';
    }
}

客户端代码还是不改变,看一下运行结果

直接一张图分析一下它的克隆原理

 运行结果分析:


 

上面两个hobbies都指向了不同对象,自然比较内存地址是不相同的

但是你想想,现在也会存在这样一个问题,就是如果一个类里面有很多引用对象,如果要进行深拷贝,我们就要不断的去进行克隆,比如如下

ConcretePrototype.java

package pxx;
import java.util.ArrayList;
import java.util.List;

public class ConcretePrototype implements Cloneable {

    private int age;
    private String name;
    //下面两个对象都是可变对象
    private List<String> hobbies;
    private BasePerson basePerson;

    @Override
    public ConcretePrototype clone() {
        try {
            //无非就是把hobbies也进行一个克隆
            //ArrayList集合对象本身就是实现了Cloneable接口的
            //所以ArrayList可以直接实现克隆方法
            //但是List接口是没有去实现这个Cloneable接口的
            ConcretePrototype result = (ConcretePrototype)super.clone();
            //把这个对象内部的集合对象也做一个克隆
            result.hobbies = (List)((ArrayList)result.getHobbies()).clone();
            //因为现在还有一个对象
            //注意BasePerson这个对象重写一下clone()方法
            result.basePerson = (BasePerson)(result.getBasePerson()).clone();
            return result;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    public BasePerson getBasePerson() {
        return basePerson;
    }

    public void setBasePerson(BasePerson basePerson) {
        this.basePerson = basePerson;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

    @Override
    public String toString() {
        return "ConcretePrototype{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", hobbies=" + hobbies +
                ", basePerson=" + basePerson +
                '}';
    }
}

//想要被克隆,必须去实现Cloneable接口
class BasePerson implements  Cloneable{
    private int age;
    private String name;

    public BasePerson(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "BasePerson{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Client.java

package pxx;

import java.util.ArrayList;
import java.util.List;

public class Client {
    public static void main(String[] args) {
        //创建原型对象
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAge(18);
        prototype.setName("Tom");
        List<String> hobbies = new ArrayList<String>();

        BasePerson basePerson = new BasePerson(20,"周杰伦");
        prototype.setBasePerson(basePerson);

        hobbies.add("书法");
        hobbies.add("美术");
        prototype.setHobbies(hobbies);

        //拷贝原型对象
        ConcretePrototype cloneType = prototype.clone();
        cloneType.setAge(520);
        cloneType.getHobbies().add("技术控");

        //这里是深拷贝,那么就是说,我们通过拷贝对象去修改BasePerson里面的值
        //不会影响原对象
        cloneType.getBasePerson().setAge(520);


        System.out.println("原型对象:" + prototype);//这里没有技术控,因为hobbies对象是两个不同对象
        System.out.println("克隆对象:" + cloneType);//这里的age与prototype也不一样,是520
        System.out.println(prototype == cloneType);//false


        System.out.println("原型对象的爱好:" + prototype.getHobbies());
        System.out.println("克隆对象的爱好:" + cloneType.getHobbies());
        System.out.println(prototype.getHobbies() == cloneType.getHobbies());//false
        //这里面表示的内存地址也不一样
        System.out.println(prototype.getBasePerson() == cloneType.getBasePerson());//false

    }

}

运行结果:

上面如果引用对象太多,克隆操作相对来说,还是比较复杂的。下面我们可以采用序列化方法来实现深度克隆,也就是我们直接把这个对象给序列化到内存里面,然后在解析出来,这样就会整体在内存里面开一个新对象与原对象无任何关系。而且,此时这个对象内部的引用对象不需要显示实现Cloneable接口,重写clone方法,与此无关

ConcretePrototype.java

package pxx;
import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class ConcretePrototype implements Cloneable,Serializable{

    private int age;
    private String name;
    //下面两个对象都是可变对象
    private List<String> hobbies;
    private BasePerson basePerson;

    @Override
    public ConcretePrototype clone() {
        //后面要用到这个流里面的数据,所以,必须拿出来
        //另外这个流不需要关闭
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)
             ){
            //你必须明白你要序列化的是哪一个对象
            objectOutputStream.writeObject(this);
        } catch (IOException e) {
            e.printStackTrace();
        }

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        try (ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream)
        ){
            //开始把对象写到内存里面去
            ConcretePrototype concretePrototype = (ConcretePrototype) objectInputStream.readObject();
            return concretePrototype;
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    public BasePerson getBasePerson() {
        return basePerson;
    }

    public void setBasePerson(BasePerson basePerson) {
        this.basePerson = basePerson;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

    @Override
    public String toString() {
        return "ConcretePrototype{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", hobbies=" + hobbies +
                ", basePerson=" + basePerson +
                '}';
    }
}

//想要被克隆,必须去实现Cloneable接口
//序列化一个类必须实现Serializable
class BasePerson implements Serializable{
    private int age;
    private String name;

    public BasePerson(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "BasePerson{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    /*@Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }*/
}

 client.java这个还是没变

package pxx;

import java.util.ArrayList;
import java.util.List;

public class Client {
    public static void main(String[] args) {
        //创建原型对象
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAge(18);
        prototype.setName("Tom");
        List<String> hobbies = new ArrayList<String>();

        BasePerson basePerson = new BasePerson(20,"周杰伦");
        prototype.setBasePerson(basePerson);

        hobbies.add("书法");
        hobbies.add("美术");
        prototype.setHobbies(hobbies);

        //拷贝原型对象
        ConcretePrototype cloneType = prototype.clone();
        cloneType.setAge(520);
        cloneType.getHobbies().add("技术控");

        //这里是深拷贝,那么就是说,我们通过拷贝对象去修改BasePerson里面的值
        //不会影响原对象
        cloneType.getBasePerson().setAge(520);


        System.out.println("原型对象:" + prototype);//这里没有技术控,因为hobbies对象是两个不同对象
        System.out.println("克隆对象:" + cloneType);//这里的age与prototype也不一样,是520
        System.out.println(prototype == cloneType);//false


        System.out.println("原型对象的爱好:" + prototype.getHobbies());
        System.out.println("克隆对象的爱好:" + cloneType.getHobbies());
        System.out.println(prototype.getHobbies() == cloneType.getHobbies());//false
        //这里面表示的内存地址也不一样
        System.out.println(prototype.getBasePerson() == cloneType.getBasePerson());//false

    }

}

运行结果

简单说一下原理

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐