java中的浅拷贝和深拷贝
java中的拷贝是什么 ?就是用Object中的clone()拷贝一个对象
在运行时刻,Object中的clone()识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。
那请问new创建一个对象和clone复制一个对象有什么区别吗?
new操作符的本意是分配内存。程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。而clone在第一步是和new相似的, 都是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。
简单举一个栗子吧:
package fuxi; class Student{ int age; String name; public Student(String name,int age) { this.name=name; this.age=age; } } public class One { public static void main(String args[]) { Student student1=new Student("小蒋", 21); Student student2=student1; System.out.println("学生1地址"+student1); System.out.println("学生2地址"+student2); } }
输出:
意思就是说 如果是引用 直接复制的话 只是把引用给复制一遍 意思就是 student1和student2的引用值是相同的 代表同一个对象。
浅拷贝:
浅拷贝(浅复制、浅克隆):被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。
MainObject1是一个对象 而MainObject2是MainObject1的浅克隆对象
Field1 Field2 是int ContainObject1是对象
如果是int之类的基本类型 则是直接赋值代表两个不同的变量 但是内容相同 操作一个对另一个不影响
如果是引用类型如对象之类的 那么操作MainObject1的ContainObject1就会影响MainObject2中的ContainObject2 因为他俩引用是指向同一个对象
注意:String是特殊情况 String 类型在传递时其实也是值传递,因为 String 类型是不可变对象。 所以如果ContainObject1是String类型 那么改变MainObject1的ContainObject1不会影响MainObject2中的ContainObject2 而是指向新的String值 具体的可以看为什么String是不可变的
栗子:
package fuxi; class Person { int age; String name; public Person(String name,int age) { this.name=name; this.age=age; } public void setAge(int a) { age=a; } public void setName(String name) { this.name=name; } } public class One implements Cloneable { String bookName; double price; Person author; public One(String bn, double price, Person author) { bookName = bn; this.price = price; this.author = author; } public Object clone() { Object b = null; try { b = super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return (One)b; } public static void main(String args[]) { Person p = new Person("Dream", 34); One book1 = new One("Java开发", 30.00, p); One book2 = (One) book1.clone(); book2.price = 44.00; book2.author.setAge(45); book2.author.setName("Fish"); book2.bookName = "Android开发"; System.out.println("age = " + book1.author.age + " name = " + book1.bookName + " price = " + book1.price); System.out.print("age = " + book2.author.age + " name = " + book2.bookName + " price = " + book2.price); } }
结果:
age = 45 name = Java开发 price = 30.0 age = 45 name = Android开发 price = 44.0
从结果中发现在改变 book2 对象的 name 和 price 属性时 book1 的属性并不会跟随改变,当改变 book2 对象的 author 属性时 book1 的 author 对象的属性也改变了,说明 author 是浅拷贝,和 book1 的 author 是使用同一引用。
1.为了获取对象的一份拷贝,我们可以利用Object类的clone()方法。 2.在派生类中覆盖基类的clone()方法,并声明为public。 (Object类中的clone()方法是protected的)。在子类重写的时候,可以扩大访问修饰符的范围 3.在派生类的clone()方法中,调用super.clone()。 因为在运行时刻,Object类中的clone()识别出你要复制的是哪一个对象,然后为此对象分配空间 并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。 4.在派生类中实现Cloneable接口。这个接口中没有什么方法,只是说明作用。 注意:继承自java.lang.Object类的clone()方法是浅复制。
clone()方法是复制对象的最快方法。这个过程需要更少的步骤来完成。
默认情况下,clone()方法提供对象的浅表副本; 即,如果我们调用super.clone(),那么它是一个浅拷贝。
clone()方法将对象复制了一份并返回给调用者。
①对任何的对象x,都有x.clone() != x;//克隆对象与原对象不是同一个对象
②对任何的对象x,都有x.clone().getClass()== x.getClass();//克隆对象与原对象的类型一样
总结 :浅拷贝 :浅拷贝对象要继承Cloneable 然后重写clone 直接返回(对象)super.clone即可
深拷贝:
深拷贝(深复制、深克隆):被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。
那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。
换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
直接看图 是全新的 对象 深拷贝后
源对象和拷贝对象之间互不影响。无论字段是否是应用类型还是原始数据类型。
那么如何实现深拷贝呢?
其实 很简单:
即 原来的person浅拷贝处理:
class Person implements Cloneable{ int age; String name; public Person(String name,int age) { this.name=name; this.age=age; } public void setAge(int a) { age=a; } public void setName(String name) { this.name=name; } public Object clone() { Object b = null; try { b = super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return (Person)b; } }
加上这句话就可以了
book2.author =(Person)author.clone(); //将Person对象进行拷贝,Person对象需进行了拷贝
即:
One book2 = (One) book1.clone(); book2.author=(Person)p.clone();
上面是用 clone() 方法实现深拷贝,传统重载clone()方法,但当类中有很多引用时,比较麻烦。 当然我们还有一种深拷贝方法,就是将对象 序列化 。
把对象写到流里的过程是序列化(Serilization)过程;而把对象从流中读出来的反序列化(Deserialization)过程。应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。
在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里,再从流里读出来,便可以重建对象。
public Object deepClone() { //将对象写到流里 ByteArrayOutoutStream bo=new ByteArrayOutputStream(); ObjectOutputStream oo=new ObjectOutputStream(bo); oo.writeObject(this); //从流里读出来 ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray()); ObjectInputStream oi=new ObjectInputStream(bi); return(oi.readObject()); }
注意: 如果拷贝类里面有类对象的话 那么那个类也得是实现Serializable接口
然后拷贝对象的时候调用deepClone()方法 来代替clone方法
One book2 =(One)book1.deepClone();
完整代码如下 : 不难 耐心看完
package fuxi; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OptionalDataException; import java.io.OutputStream; import java.io.Serializable; class Person implements Serializable { int age; String name; public Person(String name,int age) { this.name=name; this.age=age; } public void setAge(int a) { age=a; } public void setName(String name) { this.name=name; } } public class One implements Serializable { String bookName; double price; Person author; public One(String bn, double price, Person author) { bookName = bn; this.price = price; this.author = author; } public Object deepClone() throws IOException, OptionalDataException, ClassNotFoundException { // 将对象写到流里 OutputStream bo = new ByteArrayOutputStream(); ObjectOutputStream oo = new ObjectOutputStream(bo); oo.writeObject(this); // 从流里读出来 InputStream bi = new ByteArrayInputStream(((ByteArrayOutputStream) bo).toByteArray()); ObjectInputStream oi = new ObjectInputStream(bi); return (oi.readObject()); } public static void main(String args[]) throws OptionalDataException, ClassNotFoundException, IOException { Person p = new Person("Dream", 34); One book1 = new One("Java开发", 30.00, p); One book2 =(One)book1.deepClone(); book2.price = 44.00; book2.author.setAge(45); book2.author.setName("Fish"); book2.bookName = "Android开发"; System.out.println("age = " + book1.author.age + " name = " + book1.bookName + " price = " + book1.price); System.out.print("age = " + book2.author.age + " name = " + book2.bookName + " price = " + book2.price); } }
输出是:
age = 34 name = Java开发 price = 30.0 age = 45 name = Android开发 price = 44.0
总结:
浅拷贝:
深拷贝:
浅拷贝:
浅拷贝对象需要继承Cloneable接口 重写clone 返回super.clone 并且把protect改为public 依靠clone实现拷贝
调用: Student s2 = (Student)s1.clone();即可
深拷贝:
源对象和拷贝对象之间互不影响。无论字段是否是应用类型还是原始数据类型。
深拷贝对象需要继承Serializable接口 依靠序列化 自定义方法 来写入读出流
调用:One book2 =(One)book1.deepClone();
注意:序列化的时候考虑transient 因为这个修饰符可以 使被修饰的变量不被序列化 意思就是:不能序列化一个transient变量。
(具体可以参考:序列化)
使用场景有:
使用浅拷贝:对象只有原始数据类型字段;有引用类型的字段,但从不更改它。 使用深拷贝:有引用类型的字段,且经常被修改。
它非常简单,如果对象只有原始字段,那么显然你会去浅层复制,但是如果对象引用了其他对象,那么根据请求,应该选择浅拷贝或深拷贝。我的意思是,如果引用不随时修改,那么进行深层复制就没有意义了。你可以选择浅拷贝。但如果参考文件经常被修改,那么您需要进行深度复制。再次没有硬性规定,这一切都取决于要求。
问:什么我们在派生类中覆盖Object的clone()方法时,一定要调用super.clone()呢?
答:在运行时刻,Object中的clone()识别出你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。
题外话:
如果是单例模式,你以这种方式进行深拷贝,则它就不再是单例模式了。
参考:
https://blog.csdn.net/zhangjg_blog/article/details/18369201
https://www.jianshu.com/p/8c74edbb46c0
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Java中的String为什么是不可变的? -- String源码分析
转载: https://blog.csdn.net/zhangjg_blog/article/details/18319521 什么是不可变对象? 众所周知, 在Java中, String类是不可变的。那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。 区分对象和对象的引用 对于Java初学者, 对于String是不可变对象总是存有疑惑。看下面代码: String s = "ABCabc"; System.out.println("s = " + s); s = "123456"; System.out.println("s = " + s); 打印结果为: s = ABCabc s = 123456 首先创建一个String对象s,然后让s的值为“ABCabc”, 然后又让s的值为“123456”。 从打印结果可以看出,s的值确实改变了。那么怎么还说String对象...
- 下一篇
scala 下划线使用指南
原文地址:https://my.oschina.net/joymufeng/blog/863823 作者:joymufeng 下划线这个符号几乎贯穿了任何一本Scala编程书籍,并且在不同的场景下具有不同的含义,绕晕了不少初学者。正因如此,下划线这个特殊符号无形中增加Scala的入门难度。本文希望帮助初学者踏平这个小山坡。 1、用于替换Java的等价语法 由于大部分的Java关键字在Scala中拥有了新的含义,所以一些基本的语法在Scala中稍有变化。 1.1 导入通配符 *在Scala中是合法的方法名,所以导入包时要使用_代替。 //Java import java.util.*; //Scala import java.util._ 1.2 类成员默认值 Java中类成员可以不赋初始值,编译器会自动帮你设置一个合适的初始值: class Foo{ //String类型的默认值为null String s; } 而在Scala中必须要显式指定,如果你比较懒,可以用_让编译器自动帮你设置初始值: class Foo{ //String类型的默认值为null var s: Strin...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS8编译安装MySQL8.0.19
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS关闭SELinux安全模块
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7