2

前言

在现实世界中,我们通常会感觉到分身乏术。要是自己有分身那该多好啊,一个用来工作,一个用来看电视,一个用来玩游戏(无意中透露了自己单身狗的身份-。-),其实就是克隆,这种技术存在着很大的弊端,所以现在是禁止使用的。但是这种复制技术在java的世界里早已出现,就是原型模式

什么是原型模式

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象

uml类图

clipboard.png

原型模式是设计模式中最简单的,没有之一。因为它的核心就是一个clone方法,通过这个方法完成对象的克隆。java提供了cloneable接口来标示这个对象是有可能被克隆的,这个接口只具有标记作用,在jvm中只有具有这个标记的对象才有可能被克隆。有可能克隆变成可以被克隆,就需要我们重写clone方法

浅拷贝

public class Person implements Cloneable{

    public Person() {
        System.out.println("构造函数执行了");
    }

    private String name;

    public String getName() {
        return name;
    }

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

    @Override
    protected Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }
}
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person();
        person.setName("bin");

        Person person1=person.clone();
        person1.setName("123");
        System.out.println(person.getName()+"---"+person1.getName());
    }
}
测试结果:
构造函数执行了
bin---123

通过以上例子可以得知,实现cloneable接口,复写clone方法,即可对对象进行复制。有一个问题clone方法是怎么创建对象的?可以看到,构造函数只执行了一次,这就说明clone方法是不会调用构造函数的,它其实是内存二进制流的拷贝,比直接new对象性能要好很多。
标题是浅拷贝,那到底什么是浅拷贝呢?先不要着急,我们来改动下例子:

public class Person implements Cloneable{

    private List<String> valueList = new ArrayList<>();

    public void setValue(){
        valueList.add("1");
    }
    public List<String> getValue(){
        return valueList;
    }
    
    @Override
    protected Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }
}
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person();
        person.setValue();

        Person person1=person.clone();
        person1.setValue();
        System.out.println(person1.getValue());
    }
}
测试过程:
构造函数执行了
[1, 1]

或许你会很费解明明进行了拷贝,应该只会打印一个“1”啊,,为什么person1.getValue()会打印出两个“1”呢?因为java做了一个偷懒的拷贝动作,Object提供的clone方法只拷贝本对象,对其引用对象和内部数组都不拷贝,只是将地址拷贝过来用,这种拷贝方式就是浅拷贝。但是String对象例外,因为java本就希望将String看成基本数据类型,它没有clone方法,并且它的处理机制非常特殊,通过字符串池在内存中创建新的字符串,我们只要把其看待成基本数据类型就可以了。

深拷贝

当拷贝的对象中有引用对象或者数组时,我们通过浅拷贝获得复制对象是不安全的。怎么解决呢?我们可以通过深拷贝来解决这个问题,下面继续改造例子学习深拷贝:

public class Person implements Cloneable{

    private ArrayList<String> valueList = new ArrayList<>();

    public void setValue(){
        valueList.add("1");
    }
    public List<String> getValue(){
        return valueList;
    }

    @Override
    protected Person clone() throws CloneNotSupportedException {
        Person person=(Person) super.clone();
        person.valueList= (ArrayList<String>) this.valueList.clone();
        return person;
    }
}
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person();
        person.setValue();

        Person person1=person.clone();
        person1.setValue();
        System.out.println(person.getValue());
        System.out.println(person1.getValue());
    }
}
测试结果:
person:[1]
person1[1, 1]

通过对引用对象和数组的进行独立的拷贝,就完成了深拷贝,每个person对象都会有自己的valueList。

clone和final

对象的clone和对象内的final是互相冲突的,下面我们来看个图片:

clipboard.png
当我们将valueList增加final关键字,对其clone编译会报错,解决办法就是不要加final关键字(感觉像是废话...)

总结

原型模式和现实世界中说的克隆基本一样。原型模式是内存二进制流的拷贝,比new对象性能高很多,尤其是当创建这个对象需要数据库或者其他硬件资源时尤为明显。另外我们需要注意的就是当复制的对象存在引用对象和数组时,要根据实际业务选择深拷贝还是浅拷贝。


Alpaca
142 声望33 粉丝