一步步分析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
异常。
Exam
中的成员变量类Teacher
也需要实现java.lang.Serializable
接口。否则在运行时报错。
解释:
当类Exam
中包含了成员变量Teacher
时,如果只有Exam
实现java.lang.Serializable
接口,但是Teacher
没有实现java.lang.Serializable
接口,那么代码执行到os.writeObject(exam)
时还是会**抛出NotSerializableException
异常。
重点来了!使用泛型实现序列化“深拷贝”方法
public class Util {
private Util() {}
@SuppressWarnings("unchecked")
public static <T extends Serializable> 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
接口。
一步步分析Java深拷贝的两种方式-clone和序列化的更多相关文章
- java笔记线程两种方式模拟电影院卖票
public class SellTicketDemo { public static void main(String[] args) { // 创建三个线程对象 SellTicket st1 = ...
- 创建Java多线程的两种方式和线程异常
一.使用多线程的两种方法 使用多线程的两种方法有:继承Thread类和实现runable接口. 二.继承Thread类 来看一下thread类的源代码: class Thread implement ...
- Java异常处理的两种方式以及自定义异常的使用方法
异常 就是程序出现了不正常的情况 Error:严重问题,不需要处理 Exception:称为异常类,他表示程序本身可以处理的问题 RuntimeException:在编译期是不检查的,出现问题后,需要 ...
- Java 和 数据库两种方式进行加锁
java方式: publicstatic synchronized int generate(StringtableName){ Stringsql = "select value from ...
- java判断数据类型两种方式
instanceof String s = ""; System.out.println(s instanceof String); // true simp ...
- 阿里巴巴--java多线程的两种实现方式,以及二者的区别
阿里巴巴面试的时候,昨天问了我java面试的时候实现java多线程的两种方式,以及二者的区别当时只回答了实现线程的两种方式,但是没有回答上二者的区别: java实现多线程有两种方式: 1.继承Thre ...
- Java多线程的两种实现方式
Java总共有两种方式实现多线程 方式1:通过继承Thread类的方式 package com.day04; /** * 通过继承Thread类并复写run方法来是实现多线程 * * @author ...
- ORACLE数据库实现自增的两种方式
Mysql数据库因为其有自动+1,故一般我们不需要花费太多时间,直接用关键字auto_increment即可,但是Oracle不行,它没有自动增长机制.顾我们需要自己去实现.一般有两种方式,但是这两种 ...
- java 的对象拷贝(有深浅拷贝两种方式,深拷贝实现的两种方式(逐层实现cloneable接口,序列化的方式来实现))
Java提高篇--对象克隆(复制)(转自:http://www.cnblogs.com/Qian123/p/5710533.html#_label0) 阅读目录 为什么要克隆? 如何实现克隆 浅克 ...
随机推荐
- 注册表操作 Microsoft.Win32.Registry与RegistryKey类
一.注册表操作简介 Registry 类,RegistryKey 类提供了操作注册表的接口 RegistryValueKind:用于指定操作注册表的数据类型 一.注册表巢 在注册表中,最上面的节点是注 ...
- 详解Python 切片语法
Python的切片是特别常用的功能,主要用于对列表的元素取值.这篇文章主要介绍了详解Python 切片语法,需要的朋友可以参考下 Python的切片是特别常用的功能,主要用于对列表的元素取值.使用切片 ...
- Python中正则匹配使用findall,捕获分组(xxx)和非捕获分组(?:xxx)的差异
转自:https://blog.csdn.net/qq_42739440/article/details/81117919 下面是我在用findall匹配字符串时遇到的一个坑,分享出来供大家跳坑. 例 ...
- C# 任务、线程、同步(二)
取消架构 1.Parallel.For()方法的取消 static void CancelParallelLoop() { var cts = new CancellationTokenSource( ...
- Air Raid POJ - 1422 【有向无环图(DAG)的最小路径覆盖【最小不相交路径覆盖】 模板题】
Consider a town where all the streets are one-way and each street leads from one intersection to ano ...
- react 后台(一) react + redux + react-route + webpack+ axios + antd + less
create-react-app 项目名称(项目失败,ant 的样式出不来) 项目技术栈 react + redux + react-route + webpack+ axios + less + a ...
- php大文件上传
PHP用超级全局变量数组$_FILES来记录文件上传相关信息的. 1.file_uploads=on/off 是否允许通过http方式上传文件 2.max_execution_time=30 允许脚本 ...
- Reconnect due to socket error java.nio.channels.ClosedChannelException
storm整合kafka后出现如下异常: 错误原因:有部分kafka服务器连接不上导致,检查一下是不是每个kafka都能连接到(有的kafka配置使用的是host,记得配置相同的环境) 造成异常代码段 ...
- Truffle Smart Contract Error: Invalid number of parameter
I followed the tutorial of quorum with truffle: https://truffleframework.com/tutorials/building-da ...
- 会声会影x7 每次安装均会提示:已安装这个产品的另一个版本
会声会影x7 每次安装均会提示:已安装这个产品的另一个版本 卸载C++2008 的库就行了 文章来源:刘俊涛的博客 欢迎关注,有问题一起学习欢迎留言.评论