java设计模式——原型模式(克隆羊以及浅拷贝,深拷贝)

引入需求背景

有一只羊,名为: tom, 年龄为:1,颜色为:白色,请编写程序创建和 tom 羊 属性完全相同的 10只羊。

最简单的写法

public class Test {
    public static void main(String[] args) {
        Sheep sheep = new Sheep("tom",1,"白色");


        Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
        Sheep sheep4 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());

        System.out.println(sheep);
        System.out.println(sheep2);
        System.out.println(sheep3);
        System.out.println(sheep4);

    }

}

上述写法太死板了,不够灵活,我们可以做一些改进。

改进思路:Java 中 Object 类是所有类的根类,Object 类提供了一个 clone()方法,该方法可以将一个 Java 对象复制 一份,但是需要实现clone的Java类必须要实现一个接口Cloneable,该接口表示该类能够复制且具有复制的能力 => 原型模式

原型模式

  1. 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
  2. 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
  3. 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()
  4. 形象的理解:孙大圣拔出猴毛,变出其它孙大圣

所以,原型模式的核心就是一个clone()方法的操作。

通过原型模式改写克隆羊代码

步骤:

  1. 克隆羊(克隆的对象)实现Cloneable接口。
  2. 重写clone()方法。
  3. 客户端调用clone()方法,获得一个克隆对象。

Sheep.java

/**
 * 克隆羊
 * 实现Cloneable接口
 */

public class Sheep implements Cloneable {
    private String name;
    private int age;
    private String color;

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

    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 String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Sheep{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                ", hashCode='" + this.hashCode() + '\'' +
                '}';
    }


    //重写clone()
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); //通过super.clone()返回该对象的克隆实例,以Object的形式
    }
}

Test.java

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Sheep sheep = new Sheep("tom",1,"白色");


        Sheep sheep2 = (Sheep) sheep.clone();  //通过调用clone方法获取克隆对象
        Sheep sheep3 = (Sheep) sheep.clone();
        Sheep sheep4 = (Sheep) sheep.clone();

        System.out.println(sheep);
        System.out.println(sheep2);
        System.out.println(sheep3);
        System.out.println(sheep4);

//        输出结果:
//        Sheep{name='tom', age=1, color='白色', hashCode='1625635731'}
//        Sheep{name='tom', age=1, color='白色', hashCode='1580066828'}
//        Sheep{name='tom', age=1, color='白色', hashCode='491044090'}
//        Sheep{name='tom', age=1, color='白色', hashCode='644117698'}
    }


}

深拷贝和浅拷贝

接下来,我们来思考一个问题。
刚刚我们克隆对象的时候,对象的属性都是基本数据类型,那么如果是一个引用类型的话?会是一个什么样的效果??

我们做一个简单的示范,步骤:

  1. 在之前的sheep羊里加一个属性,private Sheep friend;
  2. 在客户端对这个属性进行赋值,同时clone一个新的羊出来。
  3. 同时打印原来的羊的friend和新克隆羊的friend 的 hashcode,看结果。
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Sheep friend = new Sheep("Bob", 1, "灰色"); //朋友羊,作为引用
        
        Sheep sheep = new Sheep("tom", 1, "白色", friend);
        Sheep sheep2 = (Sheep) sheep.clone();  //通过调用clone方法获取克隆对象


        System.out.println(sheep.getFriend().hashCode());  // 1625635731
        System.out.println(sheep2.getFriend().hashCode()); // 1625635731

    }

}

根据结果,我们可以看到两只羊的朋友属性引用的是同一只羊。
java设计模式——原型模式(克隆羊以及浅拷贝,深拷贝)_第1张图片

浅拷贝介绍

  1. 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
  2. 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行 引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成 员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
  3. 前面我们克隆羊就是浅拷贝
  4. 浅拷贝是使用默认的 clone()方法来实现

深拷贝介绍

  1. 复制对象的所有基本数据类型的成员变量值

  2. 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝

  3. 深拷贝实现方式1:重写clone方法来实现深拷贝

  4. 深拷贝实现方式2:通过对象序列化实现深拷贝(推荐)

通过clone() 方法实现深拷贝

需求,sheep羊有一个家,克隆羊不要引用sheep的家,要一个属于自己的家。
步骤:

  1. 创建一个huose类,实现Cloneable接口。
  2. 重写sheep的clone方法。注意,clone只能克隆基本类型,不能克隆引用类型,所以,我们要把house对象做一个单独的克隆,并进行赋值。(克隆套克隆)

huose类

public class House implements Cloneable {
    private String name;

    public House(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

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

Sheep类

public class Sheep implements Cloneable {
    private String name;
    private int age;
    private String color;

    private House house;


    public Sheep(String name, int age, String color, House house) {
        this.name = name;
        this.age = age;
        this.color = color;
        this.house = house;
    }

    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 String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }


    public House getHouse() {
        return house;
    }

    public void setHouse(House house) {
        this.house = house;
    }

    //重写clone()
    @Override
    protected Object clone() throws CloneNotSupportedException {
        //这里的克隆羊的house引用的是原先的地址
        Sheep sheep = (Sheep) super.clone();
        //把house单独拿出来克隆一份
        House house = (House) sheep.getHouse().clone();
        //再对其赋值新house的地址
        sheep.setHouse(house);
        return sheep;
    }
}

上面的重写clone方法对应的图示。
先clone()一个sheep,但是此时的sheep的引用地址是原先的。
所以我们要继续开辟一个house的克隆,再将引用替换。
java设计模式——原型模式(克隆羊以及浅拷贝,深拷贝)_第2张图片

测试类

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        House house = new House("新疆"); //房子在新疆

        Sheep sheep = new Sheep("tom", 1, "白色", house);
        Sheep sheep2 = (Sheep) sheep.clone();  //通过调用clone方法获取克隆对象


        System.out.println(sheep.getHouse().hashCode());  // 1625635731
        System.out.println(sheep2.getHouse().hashCode()); // 1580066828
        //两个对象的hashcode不一样,所以引用的是两个对象


    }

}
通过对象序列化实现深拷贝

原理:
就是将对象通过序列化,以流的方式输出(这时会将引用类型拷贝一个新的地址出来),再用输入流去接收。

步骤:
1、sheep类以及house类要实现Serializable接口,才能做一个序列化的操作。
2、在sheep类里编写deepClone()方法。
3、客户端调用deepClone()方法返回新的深拷贝对象。


public Sheep deepClone() {
        //创建流对象
         ByteArrayOutputStream bos = null;
         ObjectOutputStream oos = null;
         ByteArrayInputStream bis = null;
         ObjectInputStream ois = null;

        try {
        	//序列化
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);  //当前这个对象以对象流的方式输出
			
			//反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            return (Sheep) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            //关闭流
            try {
                bos.close();
                oos.close();
                bis.close();
                ois.close();
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }

    }

你可能感兴趣的:(Java)