版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/q258523454/article/details/88942478


花了几个小时整理了这篇文,入门浅析深拷贝和浅拷贝,希望此文让你受益!

阿里有一道这样的面试题:一个实现Cloneable的类中,clone()后的String类型数据,是深拷贝还是浅拷?

这个问题,如果换做是基本数据类型,相信你一下就脱口而出;但String是非基本类型。

String类型有点特殊,它本身没有实现Cloneable接口,故根本无法克隆,只能传递引用。在clone()后,指向的值为常量。克隆出来的对象改变他的值,实际上是改变了克隆出来对象String类型成员的指向,不会影响被克隆对象的值及其指向。这样String在拷贝的时候就表现出了“深拷贝”的特点;实际上String作为不可更改的类(immutable class),在new赋值的时候,就已经创建了一个新的对象;

如果转摘,请注明出处.


目录

什么是深浅拷贝?

1.浅拷贝

2.深拷贝——Cloneable

3.深拷贝——序列化


什么是深浅拷贝?

直白的理解就是:

浅拷贝——拷贝的太“浅”了,只拷贝对象引用,即对象的地址

深拷贝——拷贝的很“深”,复制对象的值到新开辟的空间,副本和原来的值没有任何耦合,真正意义的‘拷贝’

有以下定义:

基本数据类型:short,int,long,float,double,char,byte,boolean

特殊类型:String (这个类型比较重要)

String 存在于堆内存、常量池;这种比较特殊, 本身没有实现 Cloneable, 传递是引用地址;
由本身的final性, 每次赋值都是一个新的引用地址,原对象的引用和副本的引用互不影响。
因此String就和基本数据类型一样,表现出了"深拷贝"特性.

复合类型:不严格的说法就是指非基本类型的复合对象;当然排除JDK自带一些类型,例如Bigdecimal等等;

先看代码

代码以 学生背书包 为例, 学生类Student,书包类Bag

1.浅拷贝

public class Bag {private String name;private String color;// 省略getter/setter
}

使用Clone()方法必须实现 接口 Cloneable , 默认实现的就是浅拷贝(引用拷贝)

public class Student implements Cloneable {private String name;private int age;private Bag bag;// 省略getter/setter@Overridepublic Object clone() throws CloneNotSupportedException {return super.clone();}
}

看测试结果,一目了然

public class Test {
/*** 8种基本类型和String* Java中8种基本类型都在栈内存,clone都是深拷贝,不存在浅拷贝(引用拷贝)* 第1类:整型--》byte,short,int,long* 第2类:浮点--》float,double* 第3类:逻辑--》boolean* 第4类:字符--》char** String 存在于堆内存、常量池;这种比较特殊, 本身没有实现 Cloneable, 传递是引用地址;* 由本身的final性, 每次赋值都是一个新的引用地址,原对象的引用和副本的引用互不影响。* 因此String就和基本数据类型一样,表现出了"深拷贝"特性.*/public static void main(String[] args) throws CloneNotSupportedException {// Bag 未实现 CloneableBag bag = new Bag();bag.setName("耐克1号");bag.setColor("红色");// student 实现了 CloneableStudent student1 = new Student();String name = "张小凡";student1.setName(name);student1.setAge(18);student1.setBag(bag);// Object的clone方法(必须实现 Cloneable), 默认是浅拷贝(引用拷贝), 注意2点:// 1.student2是new地址// 2.student2中的复合类型(Bag)和student1中的(Bag)是同一个对象(引用拷贝)log.info("----------------- 浅拷贝测试1 ------------------");Student student2 = (Student) student1.clone();// false, 不是同一个地址log.info("student1 == student2: " + (student1 == student2));// true 浅拷贝, 引用拷贝log.info("student1.bag == student1.bag: " + (student1.getBag() == student2.getBag()));log.info(JSONObject.toJSONString(student1));log.info(JSONObject.toJSONString(student2));log.info("----------------- 浅拷贝测试2 ------------------");// String类型, 这里并不会改变student1和student2中的的name;// String 存在于堆内存、常量池;这种比较特殊, 本身没有实现 Cloneable, 传递是引用地址;// 由本身的final性, 每次赋值都是一个新的引用地址,原对象的引用和副本的引用互不影响。// 因此String就和基本数据类型一样,表现出了"深拷贝"特性.name = "张大凡";// student1和student2中的bag.name同时修改, 因为是浅拷贝是同一个引用地址bag.setName("耐克1号(修补)");log.info(JSONObject.toJSONString(student1));log.info(JSONObject.toJSONString(student2));// true 指向同一个bag地址log.info("bag引用地址是否相同:" + (student1.getBag() == student2.getBag()));log.info("----------------- 浅拷贝测试3 ------------------");Bag bag2 = new Bag();bag2.setName("阿迪达斯(新书包)");bag2.setColor("蓝色");// 修改stundet1的bag引用不影响student2的bagstudent1.setBag(bag2);log.info(JSONObject.toJSONString(student1));log.info(JSONObject.toJSONString(student2));// false student1的bag是新的对象log.info("bag 是否指向同一个:" + (student1.getBag() == student2.getBag()));}
}

运行如下:

----------------- 浅拷贝测试1 ------------------
student1 == student2: false
student1.bag == student1.bag: true
{"age":18,"bag":{"color":"红色","name":"耐克1号"},"name":"张小凡"}
{"age":18,"bag":{"color":"红色","name":"耐克1号"},"name":"张小凡"}
----------------- 浅拷贝测试2 ------------------
{"age":18,"bag":{"color":"红色","name":"耐克1号(修补)"},"name":"张小凡"}
{"age":18,"bag":{"color":"红色","name":"耐克1号(修补)"},"name":"张小凡"}
bag引用地址是否相同:true
----------------- 浅拷贝测试3 ------------------
{"age":18,"bag":{"color":"蓝色","name":"阿迪达斯(新书包)"},"name":"张小凡"}
{"age":18,"bag":{"color":"红色","name":"耐克1号(修补)"},"name":"张小凡"}
bag 是否指向同一个:false

2.深拷贝——Cloneable

还是上面的Student和Bag类,我们可以看到在浅拷贝Student的时候, Bag是复合数据类型,浅拷贝的是这个Bag的引用,指向同一个地址

那么深拷贝的话,同样需要将Bag复制一份到新的地址,实现Cloneable的类本身是进行深拷贝的,默认浅拷贝的方法是拷贝的该对象里面的复合数据类型,那么如果要将Student进行深拷贝,那么Bag也需要像Student一样实现Cloneable接口,并重写Clone()

修改Bag.java

public class Bag implements Cloneable {private String name;private String color;// 省略get/set@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}}

Bag也实现了Cloneable,那么在调用Student.clone()的时候,首先要使用Bag.clone()将bag的对象进行一次拷贝

public class Student implements Cloneable {private String name;private int age;private Bag bag;// 省略get/set@Overridepublic Object clone() throws CloneNotSupportedException {Student student = (Student) super.clone();// bag 需要实现 Cloneable, [强调]注意: Bag 这里只有基本数据类// 如果要实现完全深拷贝, Teacher类中只能含有非基本数据类型, 如果有非基本数据类, 那么在 bag.clone()中必须再做一次类似的深拷贝复制student.setBag((Bag) this.bag.clone());return student;}
}

测试代码:

@Slf4j
public class Test {public static void main(String[] args) throws CloneNotSupportedException {Bag bag = new Bag();bag.setName("耐克1号");bag.setColor("红色");Student student1 = new Student();student1.setName("张小凡");student1.setAge(16);student1.setBag(bag);// student2中的非基本数据类型Bag(书包)已经实现了深拷贝log.info("----------------- 深拷贝测试1 ------------------");Student student2 = (Student) student1.clone();// false, 不是同一个地址log.info("student1 == student2: " + (student1 == student2));//  false 深拷贝, 不同引用log.info("student1.bag == student2.bag: " + (student1.getBag() == student2.getBag()));log.info(JSONObject.toJSONString(student1));log.info(JSONObject.toJSONString(student2));log.info("----------------- 深拷贝测试2 ------------------");// String类型, 每次赋值都是一个新对象, 表现的就是深拷贝, 新旧对象互不影响student1.setName("王五");// 深拷贝, student1和student2中的bag是两个不同的引用, 相互独立bag.setName("耐克1号(修补)");log.info(JSONObject.toJSONString(student1));log.info(JSONObject.toJSONString(student2));// flase 深拷贝log.info("bag是否指向同一个:" + (student1.getBag() == student2.getBag())); }
}

测试结果如下:

----------------- 深拷贝测试1 ------------------
student1 == student2: false
student1.bag == student2.bag: false
{"age":16,"bag":{"color":"红色","name":"耐克1号"},"name":"张小凡"}
{"age":16,"bag":{"color":"红色","name":"耐克1号"},"name":"张小凡"}
----------------- 深拷贝测试2 ------------------
{"age":16,"bag":{"color":"红色","name":"耐克1号(修补)"},"name":"王五"}
{"age":16,"bag":{"color":"红色","name":"耐克1号"},"name":"张小凡"}
bag是否指向同一个:false

至此深拷贝和浅拷贝基本看完了,但是,我要说的是,如果Bag类型,里面也含有复合数据类型呢?直接用上面的深拷贝代码,肯定是不行,不信请看代码:

假如学生背书包,书包里面有笔(钢笔、圆珠笔、铅笔...)

public class Pen {private String type; // 笔类型private String color;// 省略getter/setter
}
public class Bag implements Cloneable {private String name;private String color;private Pen pen;// 省略getter/setter@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}}
public class Student implements Cloneable {private String name;private int age;private Bag bag;// 省略getter/setter@Overridepublic Object clone() throws CloneNotSupportedException {Student student = (Student) super.clone();student.setBag((Bag) this.bag.clone()); // bag 需要实现 Cloneablereturn student;}
}

测试代码:

public class Test {public static void main(String[] args) throws CloneNotSupportedException {Pen pen = new Pen();pen.setType("圆珠笔");pen.setColor("黑色");Bag bag = new Bag();bag.setName("耐克1号");bag.setColor("红色");bag.setPen(pen);Student student1 = new Student();student1.setName("张小凡");student1.setAge(16);student1.setBag(bag);// student2中的复合数据类型Bag(书包)已经实现了深拷贝,但是Bag中的Pen(笔)没有重写Clonable.clone(),无法深拷贝Penlog.info("----------------- 深拷贝测试1 ------------------");Student student2 = (Student) student1.clone();log.info("student1 == student2: " + (student1 == student2));                             // false, 不是同一个地址log.info("student1.bag == student2.bag: " + (student1.getBag() == student2.getBag()));   //  false 深拷贝, 不同引用// 特别注意: 下面为true, 虽然是深拷贝, 但深拷贝对象的复合数据类型仍可能含有复合数据类型,导致完全深拷贝失败, 由此我们可以发现:// clone() 深拷贝不适合嵌套对象, 特别是嵌套类型很多的情况, 因为对这些类实现深拷贝, 每一个类都需要重写Cloneable.clone()方法// 因此我们可以选择另外一种方式实现深拷贝————序列化log.info("注意: student1.bag.pen == student2.bag.pen:" + (student1.getBag().getPen() == student2.getBag().getPen()));log.info(JSONObject.toJSONString(student1));log.info(JSONObject.toJSONString(student2));log.info("----------------- 深拷贝测试2 ------------------");pen.setType("钢笔");pen.setColor("红色");log.info(JSONObject.toJSONString(student1));log.info(JSONObject.toJSONString(student2));log.info("pen是否指向同一个:" + (student1.getBag().getPen() == student2.getBag().getPen()));}
}

测试结果:

----------------- 深拷贝测试1 ------------------
student1 == student2: false
student1.bag == student2.bag: false
注意: student1.bag.pen == student2.bag.pen:true
{"age":16,"bag":{"color":"红色","name":"耐克1号","pen":{"color":"黑色","type":"圆珠笔"}},"name":"张小凡"}
{"age":16,"bag":{"color":"红色","name":"耐克1号","pen":{"color":"黑色","type":"圆珠笔"}},"name":"张小凡"}
----------------- 深拷贝测试2 ------------------
{"age":16,"bag":{"color":"红色","name":"耐克1号","pen":{"color":"红色","type":"钢笔"}},"name":"张小凡"}
{"age":16,"bag":{"color":"红色","name":"耐克1号","pen":{"color":"红色","type":"钢笔"}},"name":"张小凡"}
pen是否指向同一个:true

由此看来,我们直接这么做是无法完成深拷贝的,因为上述代码,并不是完全的深拷贝,问题就出在书包Bag中的成员Pen

既然发现了问题,那么要实现完全的深拷贝就简单了

1.将Pen.java也实现cloneable接口,并且重写clone()

2.在Bag.java中的clone()方法使用pen.clone()深拷贝一份到bag中的pen成员中

问题又来了,如果Pen中又有一个复杂数据类型呢?岂不是又要将Pen中的复杂数据类型也要做同样的操作?

是的,object的clone()方法,在深拷贝的时候,不适用于有对象多层嵌套的情况。

3.深拷贝——序列化

上面已经暴露了简单使用clone()进行深拷贝的弊端,这里介绍另一种深拷贝的方式——序列化深拷贝

所有bean必须实现 Serializable

public class Pen implements Serializable {private String type; // 笔类型private String color;// 省略get/set
}
public class Bag implements Serializable {private String name;private String color;private Pen pen;// ...
}

我们可以直接在Student中实现序列化【方法一】

public class Student implements Serializable {private String name;private int age;private Bag bag;// ...get/setpublic Object deepClone() throws IOException, ClassNotFoundException {// 序列化ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(this);oos.close();// 反序列化: 分配内存, 写入原始对象, 生成新对象ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);Object object = ois.readObject();return object;}
}

或者,我们直接将深拷贝方法包装成一个工具类【方法二:推荐该方法】:

public class CloneUtils {@SuppressWarnings("unchecked")public static <T extends Serializable> T deepClone(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;}}

测试代码:

public class Test {public static void main(String[] args) throws Exception {Pen pen = new Pen();pen.setType("圆珠笔");pen.setColor("黑色");Bag bag = new Bag();bag.setName("耐克1号");bag.setColor("红色");bag.setPen(pen);Student student1 = new Student();student1.setName("张小凡");student1.setAge(16);student1.setBag(bag);// 序列化——深拷贝// 相当于重写字节流, 再创建新对象,  跟原对象没有任何引用共享, 无需嵌套重现 Cloneable.clone(), 只需要实现 Serializable (每个子类)System.out.println("----------------- 序列化-深拷贝测试1 ------------------");// Student student2 = (Student) student1.deepClone(); // 方法一Student student2 = CloneUtils.deepClone(student1); // 方法二: 使用工具System.out.println("stu1 == stu2: " + (student1 == student2));System.out.println("stu1.bag == stu2.bag: " + (student1.getBag() == student2.getBag()));System.out.println("stu1.bag.pen == stu2.bag.pen: " + (student1.getBag().getPen() == student2.getBag().getPen()));System.out.println(JSONObject.toJSONString(student1));System.out.println(JSONObject.toJSONString(student2));System.out.println("----------------- 序列化-深拷贝测试2 ------------------");pen.setType("钢笔");pen.setColor("红色");System.out.println(JSONObject.toJSONString(student1));System.out.println(JSONObject.toJSONString(student2));}}

测试结果如下:

----------------- 序列化-深拷贝测试1 ------------------
stu1 == stu2: false
stu1.bag == stu2.bag: false
stu1.bag.pen == stu2.bag.pen: false
{"age":16,"bag":{"color":"红色","name":"耐克1号","pen":{"color":"黑色","type":"圆珠笔"}},"name":"张小凡"}
{"age":16,"bag":{"color":"红色","name":"耐克1号","pen":{"color":"黑色","type":"圆珠笔"}},"name":"张小凡"}
----------------- 序列化-深拷贝测试2 ------------------
{"age":16,"bag":{"color":"红色","name":"耐克1号","pen":{"color":"红色","type":"钢笔"}},"name":"张小凡"}
{"age":16,"bag":{"color":"红色","name":"耐克1号","pen":{"color":"黑色","type":"圆珠笔"}},"name":"张小凡"}

总结:

浅拷贝:假如 Student(含有非基本数据类型) 实现了 Cloneable , 重写了 clone()那么 Student2 student2=(Stundet) student1.clone(); 就是一个浅拷贝浅拷贝特点:1.对象本身是新对象2.对象里面的基本数据会复制, 基本数据不存在引用;特殊的String类型,有深拷贝表现;String 存在于堆内存、常量池;这种比较特殊, 本身没有实现 Cloneable, 传递是引用地址;由本身的final性, 每次赋值都是一个新的引用地址,原对象的引用和副本的引用互不影响。因此String就和基本数据类型一样,表现出了"深拷贝"特性.3.对象里面的复杂数据类型会进行浅拷贝, 指向的同一个引用地址深拷贝: 所有属性都是一份拷贝, 跟原数据不会有任何耦合(不存在引用共享)序列化深拷贝: 不需要递归让所有对象实现cloneable接口, 方便简洁

如果有问题,还望指出,我会及时更正!

Java浅拷贝和深拷贝(一文足矣),及String类型的坑。相关推荐

  1. java什么地方要用深拷贝,关于 Java 浅拷贝、深拷贝,你真的知道了吗?

    这是今天我们在技术群里面讨论的一个知识点,讨论的相当激烈,由于对这一块使用的比较少,所以对这一块多少有些盲区.这篇文章总结了所讨论的内容,希望这篇文章对你有所帮助. 在 Java 开发中,对象拷贝或者 ...

  2. Java 浅拷贝、深拷贝,你知多少?

    在 Java 开发中,对象拷贝或者说对象克隆是常有的事,对象克隆最终都离不开直接赋值.浅拷贝.深拷贝 这三种方式,其中直接赋值应该是我们最常用的一种方式吧,对于浅拷贝和深拷贝可能用的少,所以或多或少存 ...

  3. Java提高篇 —— Java浅拷贝和深拷贝

    一.前言 我们知道在Java中存在这个接口Cloneable,实现该接口的类都会具备被拷贝的能力,同时拷贝是在内存中进行,在性能方面比我们直接通过new生成对象来的快,特别是在大对象的生成上,使得性能 ...

  4. Java 浅拷贝和深拷贝的理解和实现方式

    https://www.cnblogs.com/shakinghead/p/7651502.html https://blog.csdn.net/huwentao_totti/article/deta ...

  5. Java浅拷贝和深拷贝的方式

    文章目录 1. 前言 2. 概念介绍 2.1 拷贝 / 克隆的概念 2.2 为什么需要拷贝方法? 2.3 什么是浅拷贝?浅拷贝和深拷贝的区别是什么? 3. 深拷贝的实现方式 3.1 手动深拷贝 3.2 ...

  6. java 浅拷贝和深拷贝

    实现拷贝有几点: 1)实现Cloneable接口 2)重写Object类中的clone方法,并将可见性从protect改为public 3)克隆需要调用super.clone(),也就是Object的 ...

  7. java浅拷贝和深拷贝的区别_Java 浅拷贝与深拷贝的区别

    浅拷贝 什么是浅拷贝 被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.即对象的浅拷贝会对"主"对象进行拷贝,但不会复制主对象里面的对象. ...

  8. 谈一谈Java中的深拷贝和浅拷贝

    在 Java 开发中,对象拷贝或者说对象克隆是常有的事,对象克隆最终都离不开直接赋值.浅拷贝.深拷贝 这三种方式,其中直接赋值应该是我们最常用的一种方式吧,对于浅拷贝和深拷贝可能用的少,所以或多或少存 ...

  9. java 如何实现深拷贝

    1.什么叫Java浅拷贝?  浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝.如果属性是基本类型,拷贝的就是基本类型的值:如果属性是内存地址(引用类型),拷贝的就是 ...

最新文章

  1. ItChat与图灵机器人的结合
  2. tf.squeeze
  3. Maven-学习笔记05【基础-使用骨架创建Maven的Java工程】
  4. 使用windowManager实现音乐播放器(悬浮框)效果
  5. A sample that using the completion port I/O model
  6. 利用for...in...遍历js数组与Python异同
  7. 计算机网络中什么叫总衰耗_计算机网络中的“带宽”,为什么是指“在单位时间内...
  8. python--正则表达式 字符串匹配
  9. iPhone SE 3共有三款:或将提供全面屏版本
  10. webpack-internal:///./node_modules/vue/dist/vue.esm.js:629 [Vue warn]: Invalid prop: type check fail
  11. k3c官改刷openwrt_斐讯K3刷OpenWrt(Lede)
  12. 2022年4月树莓派系统初始用户名密码
  13. JAVA开发路线走向高级开发工程师
  14. 给网站添加SSL安全证书
  15. 时光里的魔术手小故事
  16. 看山聊Java:Date 与 LocalDate 或 LocalDateTime 互相转换
  17. python爬虫———多线程threading模块爬取抖音用户信息
  18. Python 量化分析——基本面选股模型
  19. 河南计算机专业最好的独立学院,河南省排名前十的大学-河南省民办大学排名-河南省独立学院排名...
  20. 2023年网络安全专家都在用的学习资料笔记,收藏这个就够了!

热门文章

  1. 我国拟立法禁止大数据杀熟;工信部通报43款App违规整改不彻底丨钛晚报
  2. iOS-扫描二维码and条形码
  3. DNS安全(一)DNS缓存投毒与防护
  4. 迷宫寻宝(BFS模板题)
  5. Mysql多表查询效率的研究(一)
  6. java添加边框_Java如何为边框添加标题?
  7. 为什么编程入门很多人都会推荐Java?
  8. Rational Software Architect 的介绍和基础教程
  9. D神文木源:创业和做生意不同,区块链现在没有什么创业精神
  10. 模板字符串 `` + 简化对象写法