Java 深浅拷贝和原型模式

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

前言:

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

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,这种方式不好,最好是抛出异常让调用进行处理,例子只是为了调试方便才这么用。

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

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

(0)
小半的头像小半

相关推荐

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