java 序列化 clone_一步步分析Java深拷贝的两种方式-clone和序列化

今天遇到一道面试题,询问深拷贝的两种方法。主要就是clone方法和序列化方法。今天就来分析一下这两种方式如何实现深拷贝。如果想跳过解析的朋友,直奔“重点来了!”寻找答案。

clone方法

例1:我们不妨建立一个Exam对象

考试类Exam.java文件

public class Exam implements Cloneable {

private int examId;

private String examName;

public Exam() {

}

public Exam(int examId, String examName) {

this.examId = examId;

this.examName = examName;

}

public int getExamId() {

return examId;

}

public void setExamId(int examId) {

this.examId = examId;

}

public String getExamName() {

return examName;

}

public void setExamName(String examName) {

this.examName = examName;

}

@Override

protected Object clone() throws CloneNotSupportedException {

return super.clone();

}

}

测试类Main.java

public class Main {

public static void main(String[] args) throws CloneNotSupportedException {

Exam exam = new Exam(1, "语文考试");

Exam cloneExam = (Exam) exam.clone();

System.out.println(cloneExam != exam);

System.out.println(cloneExam.equals(exam));

}

}

控制台输出:

true

false

我们确实拷贝出了另一个对象。equals没有覆写,所以调用的是java.lang.Object中的以下方法:

public boolean equals(Object obj) {

return (this == obj);

}

例2:假如我们给考试加个监考老师

老师类Teacher.java,不实现Cloneable接口

public class Teacher {

private String name;

public Teacher(String name) {

this.name = name;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

}

把老师对象作为属性新增到考试类Exam.java中(设置监考老师)

public class Exam implements Cloneable {

private int examId;

private String examName;

private Teacher teacher;

public Exam() {

}

public Exam(int examId, String examName) {

this.examId = examId;

this.examName = examName;

}

public int getExamId() {

return examId;

}

public void setExamId(int examId) {

this.examId = examId;

}

public String getExamName() {

return examName;

}

public void setExamName(String examName) {

this.examName = examName;

}

public Teacher getTeacher() {

return teacher;

}

public void setTeacher(Teacher teacher) {

this.teacher = teacher;

}

@Override

public String toString() {

return "Exam{" +

"examId=" + examId +

", examName='" + examName + '\'' +

", teacher=" + teacher +

'}';

}

@Override

protected Object clone() throws CloneNotSupportedException {

return super.clone();

}

}

改写测试类Main.java

public class Main {

public static void main(String[] args) throws CloneNotSupportedException {

Exam exam = new Exam(1, "语文考试");

Teacher teacher = new Teacher("马老师");

exam.setTeacher(teacher);

Exam cloneExam = (Exam) exam.clone();

System.out.println(cloneExam != exam);

System.out.println(cloneExam.equals(exam));

cloneExam.getTeacher().setName("Lily");

System.out.println(exam.toString());

System.out.println(cloneExam.toString());

}

}

控制台输出:

true

false

Exam{examId=1, examName='语文考试', teacher=Teacher{name='Lily'}}

Exam{examId=1, examName='语文考试', teacher=Teacher{name='Lily'}}

相信眼尖的朋友已经发现端倪了,详细的分析可见下文“clone方法的存在问题”

clone方法总结:

调用clone方法的前提:

类Exam需要继承java.lang.Cloneable接口。否则代码在运行时报错。

解释:

调用exam.clone()的对象类Exam需要继承Cloneable接口,否则会在代码运行时抛出CloneNotSupportedException异常

类Exam需要覆写父类的clone()方法。否则代码在编译时报错。

解释:

因为clone()在java.lang.Object中是protected访问控制。如果不覆写,exam.clone()这句代码无法编译通过。

clone方法的存在问题:

我们从上述例2中结果中发现,我原本只想将克隆出来的考试的监考老师改为 Lily ,但是把原考试对象的监考老师也修改了,这就十分尴尬了。

阅读java.lang.Object中的clone()方法上的英文注释时有这样一段话:

*** this method creates a new instance of the class of this object and initializes all its fields with exactly the contents of the corresponding fields of this object, as if by assignment; the contents of the fields are not themselves cloned. Thus, this method performs a "shallow copy" of this object, not a "deep copy" operation. ***

翻译为:

该方法创建该对象类的新实例,并使用该对象相应字段的内容完全初始化其所有字段,就像通过赋值一样; 字段的内容本身不会被克隆。 因此,此方法执行此对象的“浅复制”,而不是“深复制”操作。

重点来了!使用clone方式实现“深拷贝”

覆写考试类Exam.java的clone()方法

@Override

protected Object clone() throws CloneNotSupportedException {

Exam exam = (Exam) super.clone();

if (teacher != null) {

Teacher teacher = (Teacher) this.teacher.clone();

exam.setTeacher(teacher);

}

return exam;

}

解析

用上述方法,取代return super.clone()的默认实现。同时因为这里调用了teacher.clone(),所以类Teacher也要实现Cloneable接口,覆写clone()方法。

改写老师类Teacher.java

public class Teacher implements Cloneable{

private String name;

public Teacher() {

}

public Teacher(String name) {

this.name = name;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

@Override

public String toString() {

return "Teacher{" +

"name='" + name + '\'' +

'}';

}

@Override

protected Object clone() throws CloneNotSupportedException {

return super.clone();

}

}

控制台输出:

true

false

Exam{examId=1, examName='语文考试', teacher=Teacher{name='马老师'}}

Exam{examId=1, examName='语文考试', teacher=Teacher{name='Lily'}}

序列化方法

每个对象覆写Cloneable方法也是够麻烦的,接下来的介绍的序列化方法更为简洁。

原理:对象->字节数组(拷贝)->对象

提到序列化,就不得不提到java.lang.Serializable,建议好好阅读一下类上的注释。

静态的序列化“深拷贝”方法(简易版)

public class Util {

private Util() {}

public static Object deepCopy(Object exam) throws IOException, ClassNotFoundException {

ByteArrayOutputStream bs = new ByteArrayOutputStream();

ObjectOutputStream os = new ObjectOutputStream(bs);

os.writeObject(exam);

ByteArrayInputStream bis = new ByteArrayInputStream(bs.toByteArray());

ObjectInputStream ois = new ObjectInputStream(bis);

return ois.readObject();

}

}

例1:考试类(无对象成员变量)

考试类对象Exam.java实现Serializable接口

public class Exam implements Serializable {

private int examId;

private String examName;

public Exam() {

}

public Exam(int examId, String examName) {

this.examId = examId;

this.examName = examName;

}

public int getExamId() {

return examId;

}

public void setExamId(int examId) {

this.examId = examId;

}

public String getExamName() {

return examName;

}

public void setExamName(String examName) {

this.examName = examName;

}

}

测试类Main.java

public class Main {

public static void main(String[] args) throws IOException, ClassNotFoundException {

Exam exam = new Exam(1, "语文考试");

Exam copyExam = (Exam) Util.deepCopy(exam);

System.out.println(copyExam != exam);

System.out.println(copyExam.equals(exam));

}

}

控制台输出:

true

false

例2:考试类(含对象成员变量)

老师类Teacher.java

public class Teacher implements Serializable {

private String name;

public Teacher() {

}

public Teacher(String name) {

this.name = name;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

@Override

public String toString() {

return "Teacher{" +

"name='" + name + '\'' +

'}';

}

}

改写Exam.java,新增成员变量teacher

public class Exam implements Serializable {

private int examId;

private String examName;

private Teacher teacher;

public Exam() {

}

public Exam(int examId, String examName) {

this.examId = examId;

this.examName = examName;

}

public int getExamId() {

return examId;

}

public void setExamId(int examId) {

this.examId = examId;

}

public String getExamName() {

return examName;

}

public void setExamName(String examName) {

this.examName = examName;

}

public Teacher getTeacher() {

return teacher;

}

public void setTeacher(Teacher teacher) {

this.teacher = teacher;

}

@Override

public String toString() {

return "Exam{" +

"examId=" + examId +

", examName='" + examName + '\'' +

", teacher=" + teacher +

'}';

}

}

改写测试类Main.java

public class Main {

public static void main(String[] args) throws IOException, ClassNotFoundException {

Exam exam = new Exam(1, "语文考试");

Teacher teacher = new Teacher("马老师");

exam.setTeacher(teacher);

Exam copyExam = (Exam) Util.deepCopy(exam);

System.out.println(copyExam != exam);

System.out.println(copyExam.equals(exam));

copyExam.getTeacher().setName("Lily");

System.out.println(exam);

System.out.println(copyExam);

}

}

控制台输出:

true

false

Exam{examId=1, examName='语文考试', teacher=Teacher{name='马老师'}}

Exam{examId=1, examName='语文考试', teacher=Teacher{name='Lily'}}

序列化方法总结

调用deepCopy方法的前提:

类Exam需要实现java.lang.Serializable接口。否则代码在运行时报错。

解释:

对象类Exam需要实现java.lang.Serializable接口,否则会在代码执行到os.writeObject(exam)时抛出NotSerializableException异常。

0082e9aa74c38e46e21ce38949f8b422.png

Exam中的成员变量类Teacher也需要实现java.lang.Serializable接口。否则在运行时报错。

解释:

当类Exam中包含了成员变量Teacher时,如果只有Exam实现java.lang.Serializable接口,但是Teacher没有实现java.lang.Serializable接口,那么代码执行到os.writeObject(exam)时还是会**抛出NotSerializableException异常。

67d18e1a00e1b4c228fba2e8627824a7.png

重点来了!使用泛型实现序列化“深拷贝”方法

public class Util {

private Util() {}

@SuppressWarnings("unchecked")

public static T deepCopy(T obj) {

T 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 = (T) ois.readObject();

ois.close();

} catch (Exception e) {

e.printStackTrace();

}

return cloneObj;

}

}

使用该方法可以在代码编译期检查出没有实现java.lang.Serializable接口的对象。

总结

clone()方法要求目标类及其成员变量类都需要实现java.lang.Cloneable接口,并且覆写java.lang.Object的clone()方法。

序列化方法通过静态方法实现,其目标类及其成员变量类都需要实现java.lang.Serializable接口。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值