java什么地方要用深拷贝,关于 Java 浅拷贝、深拷贝,你真的知道了吗?
这是今天我们在技术群里面讨论的一个知识点,讨论的相当激烈,由于对这一块使用的比较少,所以对这一块多少有些盲区。这篇文章总结了所讨论的内容,希望这篇文章对你有所帮助。
在 Java 开发中,对象拷贝或者说对象克隆是常有的事,对象克隆最终都离不开直接赋值、浅拷贝、深拷贝 这三种方式,其中直接赋值应该是我们最常用的一种方式吧,对于浅拷贝和深拷贝可能用的少,所以或多或少存在一些误区,这篇文章会详细的介绍这三种对象克隆方式。
前置知识
值类型:Java 的基本数据类型,例如 int、float
引用类型:自定义类和 Java 包装类(string、integer)
直接赋值
直接赋值是我们最常用的方式,在我们代码中的体现是Persona = new Person();Person b = a,是一种简单明了的方式,但是它只是拷贝了对象引用地址而已,并没有在内存中生成新的对象,我们可以通过下面这个例子来证明这一点
// person 对象
public class Person {
// 姓名
private String name;
// 年龄
private int age;
// 邮件
private String email;
// 描述
private String desc;
...省略get/set...
}
// main 方法
public class PersonApp {
public static void main(String[] args) {
// 初始化一个对象
Person person = new Person("张三",20,"123456@qq.com","我是张三");
// 复制对象
Person person1 = person;
// 改变 person1 的属性值
person1.setName("我不是张三了");
System.out.println("person对象:"+person);
System.out.println("person1对象:"+person1);
}
}
运行上面代码,你会得到如下结果:
person对象:Person{name='我不是张三了', age=20, email='123456@qq.com', desc='我是张三'}
person1对象:Person{name='我不是张三了', age=20, email='123456@qq.com', desc='我是张三'}
我们将 person 对象复制给了 person1 对象,我们对 person1 对象的 name 属性进行了修改,并未修改 person 对象的name 属性值,但是我们最后发现 person 对象的 name 属性也发生了变化,其实不止这一个值,对于其他值也是一样的,所以这结果证明了我们上面的结论:直接赋值的方式没有生产新的对象,只是生新增了一个对象引用,直接赋值在 Java 内存中的模型大概是这样的
浅拷贝
浅拷贝也可以实现对象克隆,从这名字你或许可以知道,这种拷贝一定存在某种缺陷,是的,它就是存在一定的缺陷,先来看看浅拷贝的定义:如果原型对象的成员变量是值类型,将复制一份给克隆对象,也就是说在堆中拥有独立的空间;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。换句话说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。 可能你没太理解这段话,那么我们在来看看浅拷贝的通用模型:
要实现对象浅拷贝还是比较简单的,只需要被复制类需要实现 Cloneable 接口,重写 clone 方法即可,对 person 类进行改造,使其可以支持浅拷贝。
public class Person implements Cloneable {
// 姓名
private String name;
// 年龄
private int age;
// 邮件
private String email;
// 描述
private String desc;
/*
* 重写 clone 方法,需要将权限改成 public ,直接调用父类的 clone 方法就好了
*/
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
...省略...
}
改造很简单只需要让 person 继承 Cloneable 接口,并且重写 clone 方法即可,clone 也非常简单只需要调用 object 的 clone 方法就好,唯一需要注意的地方就是 clone 方法需要用 public 来修饰,在简单的修改 main 方法
public class PersonApp {
public static void main(String[] args) throws Exception {
// 初始化一个对象
Person person = new Person("张三",20,"123456@qq.com","我是张三");
// 复制对象
Person person1 = (Person) person.clone();
// 改变 person1 的属性值
person1.setName("我是张三的克隆对象");
// 修改 person age 的值
person1.setAge(22);
System.out.println("person对象:"+person);
System.out.println();
System.out.println("person1对象:"+person1);
}
}
重新运行 main 方法,结果如下:
person对象:Person{name='张三', age=20, email='123456@qq.com', desc='我是张三'}
person1对象:Person{name='我是张三的克隆对象', age=22, email='123456@qq.com', desc='我是张三'}
看到这个结果,你是否有所质疑呢?说好的引用对象只是拷贝了地址,为啥修改了 person1 对象的 name 属性值,person 对象没有改变?这里就是一个非常重要的知识点了,,原因在于:String、Integer 等包装类都是不可变的对象,当需要修改不可变对象的值时,需要在内存中生成一个新的对象来存放新的值,然后将原来的引用指向新的地址,所以在这里我们修改了 person1 对象的 name 属性值,person1 对象的 name 字段指向了内存中新的 name 对象,但是我们并没有改变 person 对象的 name 字段的指向,所以 person 对象的 name 还是指向内存中原来的 name 地址,也就没有变化
这种引用是一种特列,因为这些引用具有不可变性,并不具备通用性,所以我们就自定义一个类,来演示浅拷贝,我们定义一个 PersonDesc 类用来存放person 对象中的 desc 字段,,然后在 person 对象中引用 PersonDesc 类,具体代码如下:
// 新增 PersonDesc
public class PersonDesc {
// 描述
private String desc;
}
public class Person implements Cloneable {
// 姓名
private String name;
// 年龄
private int age;
// 邮件
private String email;
// 将原来的 string desc 变成了 PersonDesc 对象,这样 personDesc 就是引用类型
private PersonDesc personDesc;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void setDesc(String desc) {
this.personDesc.setDesc(desc);
}
public Person(String name, int age, String email, String desc) {
this.name = name;
this.age = age;
this.email = email;
this.personDesc = new PersonDesc();
this.personDesc.setDesc(desc);
}
...省略...
}
修改 main 方法
public class PersonApp {
public static void main(String[] args) throws Exception {
// 初始化一个对象
Person person = new Person("平头哥",20,"123456@qq.com","我的公众号是:平头哥的技术博文");
// 复制对象
Person person1 = (Person) person.clone();
// 改变 person1 的属性值
person1.setName("我是平头哥的克隆对象");
// 修改 person age 的值
person1.setAge(22);
person1.setDesc("我已经关注了平头哥的技术博文公众号");
System.out.println("person对象:"+person);
System.out.println();
System.out.println("person1对象:"+person1);
}
}
运行 main 方法,得到如下结果:
person对象:Person{name='平头哥', age=20, email='123456@qq.com', desc='我已经关注了平头哥的技术博文公众号'}
person1对象:Person{name='我是平头哥的克隆对象', age=22, email='123456@qq.com', desc='我已经关注了平头哥的技术博文公众号'}
我们修改 person1 的 desc 字段之后,person 的 desc 也发生了改变,这说明 person 对象和 person1 对象指向是同一个 PersonDesc 对象地址,这也符合浅拷贝引用对象只拷贝引用地址并未创建新对象的定义,到这你应该知道浅拷贝了吧。
深拷贝
深拷贝也是对象克隆的一种方式,相对于浅拷贝,深拷贝是一种完全拷贝,无论是值类型还是引用类型都会完完全全的拷贝一份,在内存中生成一个新的对象,简单点说就是拷贝对象和被拷贝对象没有任何关系,互不影响。深拷贝的通用模型如下:
深拷贝有两种方式,一种是跟浅拷贝一样实现 Cloneable 接口,另一种是实现 Serializable 接口,用序列化的方式来实现深拷贝,我们分别用这两种方式来实现深拷贝
实现 Cloneable 接口方式
实现 Cloneable 接口的方式跟浅拷贝相差不大,我们需要引用对象也实现 Cloneable 接口,具体代码改造如下:
public class PersonDesc implements Cloneable{
// 描述
private String desc;
...省略...
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Person implements Cloneable {
// 姓名
private String name;
// 年龄
private int age;
// 邮件
private String email;
private PersonDesc personDesc;
/**
* clone 方法不是简单的调用super的clone 就好,
*/
@Override
public Object clone() throws CloneNotSupportedException {
Person person = (Person)super.clone();
// 需要将引用对象也克隆一次
person.personDesc = (PersonDesc) personDesc.clone();
return person;
}
...省略...
}
main 方法不需要任何改动,我们再次运行 main 方法,得到如下结果:
person对象:Person{name='平头哥', age=20, email='123456@qq.com', desc='我的公众号是:平头哥的技术博文'}
person1对象:Person{name='我是平头哥的克隆对象', age=22, email='123456@qq.com', desc='我已经关注了平头哥的技术博文公众号'}
可以看出,修改 person1 的 desc 时对 person 的 desc 已经没有影响了,说明进行了深拷贝,在内存中重新生成了一个新的对象。
实现 Serializable 接口方式
实现 Serializable 接口方式也可以实现深拷贝,而且这种方式还可以解决多层克隆的问题,多层克隆就是引用类型里面又有引用类型,层层嵌套下去,用 Cloneable 方式实现还是比较麻烦的,一不小心写错了就不能实现深拷贝了,使用 Serializable 序列化的方式就需要所有的对象对实现 Serializable 接口,我们对代码进行改造,改造成序列化的方式
public class Person implements Serializable {
private static final long serialVersionUID = 369285298572941L;
// 姓名
private String name;
// 年龄
private int age;
// 邮件
private String email;
private PersonDesc personDesc;
public Person clone() {
Person person = null;
try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
person = (Person) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return person;
}
public void setDesc(String desc) {
this.personDesc.setDesc(desc);
}
...省略...
}
public class PersonDesc implements Serializable {
private static final long serialVersionUID = 872390113109L;
// 描述
private String desc;
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
public class PersonApp {
public static void main(String[] args) throws Exception {
// 初始化一个对象
Person person = new Person("平头哥",20,"123456@qq.com","我的公众号是:平头哥的技术博文");
// 复制对象
Person person1 = (Person) person.clone();
// 改变 person1 的属性值
person1.setName("我是平头哥的克隆对象");
// 修改 person age 的值
person1.setAge(22);
person1.setDesc("我已经关注了平头哥的技术博文公众号");
System.out.println("person对象:"+person);
System.out.println();
System.out.println("person1对象:"+person1);
}
}
运行 main 方法,我们可以得到跟 Cloneable 方式一样的结果,序列化的方式也实现了深拷贝。到此关于 Java 浅拷贝和深拷贝的相关内容就介绍完了,希望你有所收获。
最后
目前互联网上很多大佬都有 Java 对象克隆文章,如有雷同,请多多包涵了。原创不易,码字不易,还希望大家多多支持。若文中有所错误之处,还望提出,谢谢。
平头哥的技术博文(id:pingtouge_java)
作者:平头哥,学会伺机而动,实现弯道超车
java什么地方要用深拷贝,关于 Java 浅拷贝、深拷贝,你真的知道了吗?相关推荐
- java clone 深拷贝_Java clone() 浅拷贝 深拷贝
假如说你想复制一个简单变量.很简单: int apples = 5;int pears = apples; 不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,f ...
- 【Java基础系列教程】第八章 Java面向对象详解(三)_抽象类、接口、内部类、深拷贝与浅拷贝
一.JavaBean规范 1.1 什么是JavaBean JavaBeans是Java中一种特殊的类,可以将多个对象封装到一个对象(bean)中.特点是可序列化,提供无参构造器,提供getter方法和 ...
- java对象深克隆_JAVA中对象的克隆及深拷贝和浅拷贝
使用场景: 在日常的编程过程 中,经常会遇到,有一个对象OA,在某一时间点OA中已经包含了一些有效值 ,此时可能会需一个和OA完全相对的新对象OB,并且要在后面的操作中对OB的任何改动都不会影响到OA ...
- 浅拷贝和深拷贝(谈谈java中的clone)
clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那么在java语言中,有 ...
- 梅州有学java的地方吗,梅州java工资水平,梅州java工资很高吗,梅州java工资底薪能到多少...
梅州java工资水平,梅州java工资很高吗,梅州java工资底薪能到多少 首页 > java > 梅州java工资水平 作者:镀金池 发布时间:2019-02-20 02:20 以是 ...
- java主要用于神摸地方,超级无敌神奇的java期末考试题库
<超级无敌神奇的java期末考试题库>由会员分享,可在线阅读,更多相关<超级无敌神奇的java期末考试题库(20页珍藏版)>请在人人文库网上搜索. 1.一.选择题1.已知X和Y ...
- 【JAVA零基础入门系列】Day14 Java对象的克隆
[JAVA零基础入门系列](已完结)导航目录 Day1 开发环境搭建 Day2 Java集成开发环境IDEA Day3 Java基本数据类型 Day4 变量与常量 Day5 Java中的运算符 Day ...
- 如何java面试_短时间如何过java面试?
这题我会!作为一个编程界老司机,我曾总结过一套Java常见的面试考点大全,不知道帮助过多少程序员拿下offer. 现在我把这套Java面试大全放出来,希望对大家有所帮助! 本文内容过长,建议大家先赞后 ...
- 怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版,每个 Java 初学者都应该收藏
这么说吧,在我眼里,Java 就是最流行的编程语言,没有之一(PHP 往一边站).不仅岗位多,容易找到工作,关键是薪资水平也到位,不学 Java 亏得慌,对吧? 那可能零基础学编程的小伙伴就会头疼了, ...
- 双表查询java代码_什么是JDBC?Java数据库连接性简介
JDBC(Java数据库连接性)是Java API,用于管理与数据库的连接,发出查询和命令以及处理从数据库获得的结果集.JDBC在1997年作为JDK 1.1的一部分发布,是为Java持久层开发的首批 ...
最新文章
- MULLS:一种基于多尺度线性最小二乘的激光SLAM算法
- 第十四节: 介绍四大并发集合类并结合单例模式下的队列来说明线程安全和非安全的场景及补充性能调优问题。...
- 肾炎治疗有效方(湿热壅滞三焦,气机不利)
- TensorFlow 1.2正式发布,新增Python 3.6支持
- jboss启动初始页面_JBoss BRMS最佳实践– BPM流程初始化层的提示
- 品牌不可不知的YouTube的影片行销策略
- c语言中sizeof是一个标准库函数,对C语言中的sizeof的介绍分析
- oracle 11g rman catalog,Oracle 11g RMAN复制数据库的测试
- 错乱的 Windows 10
- Android Studio两模块间getLaunchIntentForPackage跳转,出现intent为null,已解决
- 读懂mysql慢查询日志
- EXPDP Fails With ORA-04031 (streams pool, ...)
- 安装conntrack-tools
- freeSWITCH中动态加载模块
- 前端调试debugf_前端应该掌握的这些Debug技巧,能够快速提升你的前端开发能力...
- ADB远程桌面连接本地手机
- 用javascript实现调用本地用户程序
- win10user文件夹迁移_Win10纯净版下迁移用户文件的技巧
- python面向对象编程指南 豆瓣_Python面向对象编程
- 合并两张图片php,php多张图片合并方法分享
热门文章
- 啊哈 算法 Java_《啊哈!算法》.啊哈磊.高清版.pdf
- 探花交友_第12章_实现推荐系统(新版)
- 随手拍好片如何炼成?用完小米10至尊纪念版我就明白了
- 操作系统 第4章 习题整理
- ArcGIS Engine开发教程之图层符号化(四)——符号化之Renderer( 渲染)体系
- MySQL 进阶 视图 -- 视图介绍、视图CRUD语法、检查选项(CASCADED、LOCAL)、视图的更新、视图作用、视图案例
- angular4 使用HttpClient拦截器 检查token失效,返回登录页面
- 常被大众误以为是外国佬研发的四款国产软件,建议低调收藏
- Java多线程案例——线程池
- 【Django】开发日报_3_Day:员工管理系统-创建项目