原型模式是用于创建重复的对象,同时又能保证性能,通过复制现有实例来创建新的实例,无需知道类的信息。

与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。

那么java中是如何实现原型模式的呢?原型模式的本质就是克隆,拷贝一个一模一样的对象。

java中的实现原型模式可以分为两种,一种是浅拷贝,一种是深拷贝。

浅拷贝实现原型模式就是实现了一个克隆接口,该接口就是用于创建当前对象的克隆。下面通过代码来实现浅拷贝。

 首先定义一个类,这个类实现Cloneable接口里面的clone()方法。

public class Person implements Cloneable {

    String name;
    int age;
    int[] a;
    @Override
    public Object clone() throws CloneNotSupportedException{
        Object obj = super.clone();
        return obj;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public int[] getA() {
        return a;
    }

    public void setA(int[] a) {
        this.a = a;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", a=" + Arrays.toString(a) +
                '}';
    }
}
 public static void main(String[] args) throws CloneNotSupportedException{
        Person p = new Person();
        p.setAge(10);
        p.setName("Jack");
        p.setA(new int[]{1});
        System.out.println("克隆前p的值:"+p);
        Person p2 = (Person)p.clone();
        System.out.println("克隆前未修改p2的值:"+p2);
        p2.setAge(11);
        p2.setName("Tom");
        int[] a = p2.getA();
        a[0] = 2;
        p2.setA(a);
        System.out.println("克隆后修改p2的值,p2的值为:"+p2);
        System.out.println("克隆后修改p2的值,p的值为:"+p);
    }

克隆前p的值:Person{name='Jack', age=10, a=[1]}
克隆前未修改p2的值:Person{name='Jack', age=10, a=[1]}
克隆后修改p2的值:Person{name='Tom', age=11, a=[2]}
克隆后修改p2的值,p的值为:Person{name='Jack', age=10, a=[2]}

通过上面这一段代码,我们发现,修改由p克隆出来的p2的String和int类型的值,p的对应的类型的值并没有发生改变,但是修改引用类型int[] 的值时,p对应的类型也发生了改变,这就说明,对于基本类型而言,浅拷贝,对值类型的成员变量进行值的复制,对于引用类型的变量进行引用的复制,不复制引用的对象。String是一个特殊的类型,它的数据是放在常量池中的,对p2的修改是将它String类型的引用从“Jack”指向了“Tom”,而p的String类型的指向仍然是“Jack”,并没有发生改变。

那么,有很多时候,我并不希望对拷贝对象的修改会影响到原来的对象,这时候我们就需要进行深拷贝了。

深拷贝:对值类型的成员变量进行值拷贝,对引用类型的变量进行引用的复制,并且复制引用的对象。简单点来说,所谓的深拷贝就是对于那些只是拷贝引用而不拷贝对象的属性进行单独拷贝。

第一种方法:将引用类型的对象再单独拷贝一下

public class Person implements Cloneable {

    String name;
    int age;
    int[] a;
    @Override
    public Person clone() throws CloneNotSupportedException{

        Person person = (Person)super.clone();
        person.a = this.a.clone();
        return person;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public int[] getA() {
        return a;
    }

    public void setA(int[] a) {
        this.a = a;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", a=" + Arrays.toString(a) +
                '}';
    }
}

直接在clone()方法中将int[]数组类型的引用单独再拷贝一份,即可实现深拷贝。

第二种方法:使用javaIO流的方式,将对象序列化为字节序列后,默认会将该对象的整个对象图进行序列化,再通过反序列即可完美地实现深拷贝。

public static void main(String[] args) throws Exception{
        Person person = new Person();
        person.setAge(10);
        person.setName("Jack");
        person.setA(new int[]{1});

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        //ObjectOutputStream 对象输出流
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(person);
        oos.flush();
        //ObjectOutputStream 对象输入流
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
        Person person2 = (Person)ois.readObject();
        System.out.println("深拷贝后修改person2的值:"+person2);
        person2.setName("Tom");
        person2.setAge(12);
        int[] a = person2.getA();
        a[0] = 3;
        person2.setA(a);
        System.out.println("深拷贝后修改person2的值:"+person2);
        System.out.println("深拷贝后修改person2的值,person的值为:"+person);
    }

深拷贝后修改person2的值:Person{name='Jack', age=10, a=[1]}
深拷贝后修改person2的值:Person{name='Tom', age=12, a=[3]}
深拷贝后修改person2的值,person的值为:Person{name='Jack', age=10, a=[1]}

我们看到,深拷贝后,修改person2的值并未影响到person的值。需要注意的是,如果有属性使用transient关键词修饰的话,这个属性是不会被序列化的。

04-07 17:56