[设计模式]原型模式 涉及浅拷贝,深拷贝

导读:本篇文章讲解 [设计模式]原型模式 涉及浅拷贝,深拷贝,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

原型模式(Prototype Pattern)

原型模式实际上就是实现 Cloneable 接口,重写 clone()方法,然后去复制出一个个对象。
即通过克隆快速构建出参数一样的对象。

使用原型模式的优点:

● 性能优良
原型模式是在内存二进制流的拷贝,要比直接 new 一个对象性能好很多,特别是
要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
● 逃避构造函数的约束
这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的

说到java中的克隆,必要的一个方法就是Object类中native clone方法。

使用场景:

● 资源优化场景
类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
● 性能和安全要求的场景
通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
● 一个对象多个修改者的场景
一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
原型模式 拷贝分为 浅拷贝和深拷贝。

浅拷贝和深拷贝介绍

protected native Object clone() throws CloneNotSupportedException;

它是Object中的方法,这意味所有的类都可以实现这一方法。因为所有的类都隐式继承了Object。

注意:对象想要使用clone()必须要继承Cloneable接口.否则将抛出CloneNotSupportedException异常.

使用场景

比如说我们需要许多的奖状,同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,

难道我们要一个一个new 奖状(“XX”,msg); 这样一系列操作吗?

在参数较少的情况下,麻烦程度可能不太显现,那如果一个对象的参数设置就有许多,那就太麻烦了.

这时我们就可以使用clone方法快捷的创建对象。

就像这样:

Citation c1 = new Citation();
Student stu = new Student("张三", "西安");
c1.setStu(stu);
//复制奖状
Citation c2 = c1.clone();
//获取c2奖状所属学生对象
Student stu1 = c2.getStu();
stu1.setName("李四");

我们只需要复制多个“三好学生”奖 状出来,然后在修改奖状上的名字即可。

所以对于对象的创建非常复杂,我们可以使用clone()快捷的创建对象。

这也就是设计模式中原型模式的思想.

使用clone 和 浅克隆

举个例子:

我们有一个Man对象,继承Cloneable接口,重写clone方法,并使用clone方法复制.

class Man implements Cloneable {
    String name;
    int age;
	Son son;  //一个对象 其类拥有name和age属性
    
    public Man(String name, int age, Son son) {
        this.age = age;
        this.name = name;
        this.son = son;
    }

...省略name和age,son的get set方法 和重写的toString方法
    
//重写clone方法
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Main方法:

public static void main(String[] args) throws CloneNotSupportedException {
	Son son = new son("小明",12);
    Man man1 = new Man("明他爸",35,son);//new 一个Man对象
    
    Man man2 = (Man) man1.clone(); //使用clone
    Man man3 = (Man) man1.clone();  

    System.out.println(man1);  //打印对象
    System.out.println(man2);
    System.out.println(man3);
    
    System.out.println(man1==man2);//判断是否是同一个对象
    System.out.println(man2==man3);
       
    System.out.println(man1.son==man2.son);//判断son属性对象地址是否相同
    System.out.println(man2.son==man3.son);
}

结果:

Man{name='明他爸', age=35, son=Son{name='小明', age=12}}  //属性值相同
Man{name='明他爸', age=35, son=Son{name='小明', age=12}}
Man{name='明他爸', age=35, son=Son{name='小明', age=12}}
false          //Main对象地址不相同
false
true           //son属性对象地址相同
true

可以看到使用clone()复制的对象,它们的属性值相等,但是对象地址不相同,意味这他们拥有不同的堆空间.不是同一个对象.

但是Main类中属性Son对象却是同一个对象.他们有相同的地址.

这也就是浅克隆.

结论:单纯使用clone即super.clone();创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有 属性所指向的对象的内存地址.

向上面使用浅克隆,复制出许多不同Man对象,而son对象却只有一个. 这么多爸爸只有一个儿子,那可就太惨了.🤣🤣🤣

当我修改对象中的非基本类型属性时,修改一个将会影响所有的克隆对象,因为对象中的非基本类型属性都是同一个.这将是一个非常严重的安全问题.

怎么解决呢?这就需要深克隆了.

深克隆是完完全全的克隆.复制整个对象信息,包含值类型和引用类型

两种深克隆的实现

深克隆的实现通常有两种方法:

  • 序列化实现深克隆:先将原对象序列化到内存的字节流中,再从字节流中反序列化出刚刚存储的对象,这个新对象和原对象就不存在任何地址上的共享,这样就实现了深克隆。
  • 所有引用类型都实现克隆:要复制对象的所有引用类型都要实现克隆,所有对象都是复制的新对象,从而实现了深克隆。

深克隆实现方式一:序列化(推荐)

实现思路:先将要拷贝对象写入到内存中的字节流中,然后再从这个字节流中读出刚刚存储的信息,作为一个新对象返回,那么这个新对象和原对象就不存在任何地址上的共享,自然实现了深拷贝。

注意类要实现 Serializable 接口

请参考以下代码:

class CloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        BirdChild birdChild = new BirdChild();
        birdChild.name = "小小鸟";
        Bird bird = new Bird();
        bird.name = "小鸟";
        bird.birdChild = birdChild;
        // 使用序列化克隆对象
        Bird bird2 = CloneUtils.clone(bird);
        bird2.name = "黄雀";
        bird2.birdChild.name = "小黄雀";
        System.out.println("bird name:" + bird.name);
        System.out.println("bird child name:" + bird.birdChild.name);
        System.out.println("bird name 2:" + bird2.name);
        System.out.println("bird child name 2:" + bird2.birdChild.name);
    }
}
class CloneUtils {
    public static <T extends Serializable> T clone(T obj) {
        T cloneObj = null;
        try {
            //写入字节流
            ByteArrayOutputStream bo = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bo);
            oos.writeObject(obj);
            oos.close();
            //分配内存,写入原始对象,生成新对象
            ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());//获取上面的输出字节流
            ObjectInputStream oi = new ObjectInputStream(bi);
            //返回生成的新对象
            cloneObj = (T) oi.readObject();
            oi.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cloneObj;
    }
}

程序执行结果:

bird name:小鸟
bird child name:小小鸟
bird name 2:黄雀
bird child name 2:小黄雀

深克隆实现方式二:所有引用类型都实现克隆

学生类:

public class Student implements Cloneable {
    private String name;
    private int age;
    public Student(String name, int age){
        this.name = name;
        this.age = age;
    }

    @Override
    protected Object clone()  {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
    ...省略Student 的set  get方法

}

老师类:

public class Teacher implements Cloneable{
    private String name;
    private int age;
    private Student student;

    public Teacher(String name, int age, Student student){
        this.name = name;
        this.age = age;
        this.student = student;
    }
    ...省略Teacher类get set方法
    // 覆盖
    @Override
    public Object clone() {
        Teacher t = null;
        try {
            t = (Teacher) super.clone();
            t.student = (Student)student.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return t;
    }
 	
}

测试:

public class test {
    public static void main(String[] args) {
        Student s = new Student("学生1", 11);
        Teacher origin = new Teacher("老师原对象", 23, s);
        
        System.out.println("克隆前的学生姓名:" + origin.getStudent().getName());
        
        Teacher clone = (Teacher) origin.clone();//调用克隆方法
        
        // 更改克隆后的学生信息 更改了姓名
        clone.getStudent().setName("我是克隆对象更改后的学生2");
        System.out.println("克隆后的学生姓名:" + clone.getStudent().getName());
    }
}

运行结果:

克隆前的学生姓名:学生1
克隆后的学生姓名:我是克隆对象更改后的学生2

总结

  • 浅克隆:只会复制对象的值类型,而不会复制对象的引用类型;
  • 深克隆:复制整个对象,包含值类型和引用类型。
  1. 克隆可分为浅克隆和深克隆,实际应用中一般使用深克隆
  2. 深克隆有两种实现方法:
    • 使用序列化(推荐)
    • 所有引用类型都实现克隆
  3. 使用clone()方法需要实现Cloneable接口,否则将抛出CloneNotSupportedException异常.
  4. 原型模式就是使用克隆,快捷创建对象.
  5. Object中clone()方法是一个native 方法,也就是原生函数,本地方法,使用操作系统底层的语言实现的,因此执行效率更高

扩展

Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等。

还有什么问题小伙伴们可以留言讨论

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/63407.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!