前言:
本来是在学习原型模式的,后来发现它就是一个拷贝,然后就去研究了下深浅拷贝以及它们的实现,这里不说大道理,尽量通俗易懂地把它们都讲清楚。
1.引入
问:java 对象拷贝的意义何在?为啥要拷贝?
答:因为懒,不想实例化一个,所以拷贝生成一个新的对象
现实实例:
本科学习的时候有很多课程是讲 ppt 进行考核的,有些人忙(其实嘛…)没做 ppt,所以就会拷贝一份室友(室长)的;A室友拷贝了一份 ppt 就放在室长电脑里面同一个目录下,改了些个人信息,并把 ppt 链接的素材裁剪了一番;B室友把 ppt 和素材都拷贝到自己电脑上了,然后对链接的素材修剪了一番。
- 上面例子的拷贝就是此次博客说的拷贝
- 浅拷贝:A室友修改素材的操作会影响到室长吗?当然会,室长讲 ppt 打开素材的时候内心一定会想,猪…队友;B室友的动作就是浅拷贝,修改了 ppt 本身的属性是可以的,但修改其链接素材会同步改变室长 ppt 的链接素材。
- 深拷贝: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