Java 深浅拷贝和原型模式

前言:

本来是在学习原型模式的,后来发现它就是一个拷贝,然后就去研究了下深浅拷贝以及它们的实现,这里不说大道理,尽量通俗易懂地把它们都讲清楚。

1.引入

问:java 对象拷贝的意义何在?为啥要拷贝?
答:因为懒,不想实例化一个,所以拷贝生成一个新的对象

现实实例:
本科学习的时候有很多课程是讲 ppt 进行考核的,有些人忙(其实嘛…)没做 ppt,所以就会拷贝一份室友(室长)的;A室友拷贝了一份 ppt 就放在室长电脑里面同一个目录下,改了些个人信息,并把 ppt 链接的素材裁剪了一番;B室友把 ppt 和素材都拷贝到自己电脑上了,然后对链接的素材修剪了一番。

  1. 上面例子的拷贝就是此次博客说的拷贝
  2. 浅拷贝:A室友修改素材的操作会影响到室长吗?当然会,室长讲 ppt 打开素材的时候内心一定会想,猪…队友;B室友的动作就是浅拷贝,修改了 ppt 本身的属性是可以的,但修改其链接素材会同步改变室长 ppt 的链接素材。
  3. 深拷贝:B室友的拷贝 ppt 就是深拷贝,和室长的 ppt 可以独立修改,互不影响。
2.浅拷贝的实现

本质上使用的是 java 自带的 clone() 方法
这里举一个例子,Book类(课本),Subject类(课程)包含Book

Book类

public class Book {

    private String bookName;
    private int page;

    public Book(String bookName, int page) {
        this.bookName = bookName;
        this.page = page;
    }

    public String getBookName() {
        return bookName;
    }

    public int getPage() {
        return page;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public void setPage(int page) {
        this.page = page;
    }

}

Subject类,需要实现 Cloneable 接口的 clone()方法才能被拷贝

public class Subject implements Cloneable {
    private Book book;
    private String subjectName;

    public Book getBook() {
        return book;
    }

    public void setBook(Book book) {
        this.book = book;
    }

    public String getSubjectName() {
        return subjectName;
    }

    public void setSubjectName(String subjectName) {
        this.subjectName = subjectName;
    }

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

}

测试

public class Main {
    public static void main(String[] args) {
        Subject music = new Subject();
        music.setBook(new Book("五线谱", 50));
        music.setSubjectName("音乐课");

        Subject sports = (Subject) music.clone();
        sports.setSubjectName("体育课");
        sports.getBook().setBookName("体操讲义");

        //验证浅拷贝
        System.out.print("课程名:" + sports.getSubjectName() + ",");
        System.out.println("课本名:" + sports.getBook().getBookName());
        System.out.print("课程名:" + music.getSubjectName() + ",");
        System.out.println("课本名:" + music.getBook().getBookName());
    }

}

打印结果:

课程名:体育课,课本名:体操讲义
课程名:音乐课,课本名:体操讲义

解析:
这边最初实例化一个“音乐课”的课程,并给了一本“五线谱”的书,在此基础上,拷贝产生了一个新对象,并修改课程名称为“体育课”,修改课本为“体操讲义”;把他们打印出来就会发现,课程名(subjectName)改变后相互不影响,课本(book)做了同步修改,浅拷贝就是这种情况,当然这种在生产上也有很大用处。

3.所有类都实现自身拷贝的深拷贝方式

2 中的例子是 Subject 实现拷贝本身,如果Subject 的拷贝方法把 Book 的拷贝也实现是不是可以实现深拷贝?答案是可以的,但是 Book 也需要实现自身的拷贝函数,eg:

public class Book implements Cloneable {

    private String bookName;
    private int page;

    public Book(String bookName, int page) {
        this.bookName = bookName;
        this.page = page;
    }

    public String getBookName() {
        return bookName;
    }

    public int getPage() {
        return page;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public void setPage(int page) {
        this.page = page;
    }

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

}



public class Subject implements Cloneable {
    private Book book;
    private String subjectName;

    public Book getBook() {
        return book;
    }

    public void setBook(Book book) {
        this.book = book;
    }

    public String getSubjectName() {
        return subjectName;
    }

    public void setSubjectName(String subjectName) {
        this.subjectName = subjectName;
    }

    @Override
    public Object clone() {
        try {
            Subject subject = (Subject) super.clone();
            subject.setBook((Book) book.clone());   // 调用Book 的拷贝方法拷贝 Book
            return subject;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

}

测试:

public class Main {
    public static void main(String[] args) {
        Subject music = new Subject();
        music.setBook(new Book("五线谱", 50));
        music.setSubjectName("音乐课");

        Subject sports = (Subject) music.clone();
        sports.setSubjectName("体育课");
        sports.getBook().setBookName("体操讲义");

        //验证浅拷贝
        System.out.print("课程名:" + sports.getSubjectName() + ",");
        System.out.println("课本名:" + sports.getBook().getBookName());
        System.out.print("课程名:" + music.getSubjectName() + ",");
        System.out.println("课本名:" + music.getBook().getBookName());

    }

}

结果:

课程名:体育课,课本名:体操讲义
课程名:音乐课,课本名:五线谱

保证了互不影响

4.序列化实现深拷贝

上述3的方式把每个类都实现自身拷贝的方法有点麻烦,有简单的吗?有,序列化实现拷贝,只需要类都实现接口声明就行了,不需要写实现方法,如下
不建议把序列化方法写成类成员函数,通用性太差,建议写成模板类方法,可以实现复用

import java.io.*;

public class SerializedClone {

    @SuppressWarnings("unchecked")
    public static <X extends Serializable> X clone(X obj) {
        X cloneObj = null;
        try {
            //写入字节流
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream obs = new ObjectOutputStream(out);
            obs.writeObject(obj);
            obs.close();

            //分配内存,写入原始对象,生成新对象
            ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(ios);
            //返回生成的新对象
            cloneObj = (X) ois.readObject();
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cloneObj;
    }
}

还是那两个类 Book 和 Subject,只需要声明接口 Serializable 就行,不需要方法实现(Cloneable 需要实现 clone() 方法)

public class Book implements Serializable {
    private String bookName;
    private int page;

    public Book(String bookName, int page) {
        this.bookName = bookName;
        this.page = page;
    }

    public String getBookName() {
        return bookName;
    }

    public int getPage() {
        return page;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public void setPage(int page) {
        this.page = page;
    }
}



public class Subject implements Serializable {

    private Book book;
    private String subjectName;

    public Book getBook() {
        return book;
    }

    public void setBook(Book book) {
        this.book = book;
    }

    public String getSubjectName() {
        return subjectName;
    }

    public void setSubjectName(String subjectName) {
        this.subjectName = subjectName;
    }

}

测试:

public class Main {
    public static void main(String[] args) {

        Subject chemistry = new Subject();
        chemistry.setBook(new Book("火药制造", 50));
        chemistry.setSubjectName("化学课");

        Subject physics = SerializedClone.clone(chemistry);
        physics.setSubjectName("物理课");
        physics.getBook().setBookName("穿墙讲义");

        //验证深拷贝
        System.out.print("课程名:" + chemistry.getSubjectName() + ",");
        System.out.println("课本名:" + chemistry.getBook().getBookName());
        System.out.print("课程名:" + physics.getSubjectName() + ",");
        System.out.println("课本名:" + physics.getBook().getBookName());
    }
}

结果:

课程名:化学课,课本名:火药制造
课程名:物理课,课本名:穿墙讲义

可以发现,序列化实现深拷贝更简单

5.其它方式

还有一种反射的方式实现拷贝,实现起来稍微麻烦,有兴趣可以研究

:上述例子在处理 exception 的时候返回 null,这种方式不好,最好是抛出异常让调用进行处理,例子只是为了调试方便才这么用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值