前言
记录学习过程
目录
复制
概念
复制可以分为3种:直接赋值复制、浅拷贝、深拷贝
它们大概的概念:
直接赋值复制:对基本数据类型进行值传递,通过赋值将一个对象的引用复制给另一个对象
浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝
深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝
为什么要复制,不直接new一个?
new来的对象的属性只是初始化时的对象属性
而我们可能需要改变属性或者赋值属性后的对象,就可以复制拷贝需要的对象
直接赋值复制
创建一个Person类
package com.company.NewEntity;
public class Person {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
测试类:
package com.company.NewEntity;
public class TestPerson {
public static void main(String[] args){
Person person=new Person(1,"zhangsan");
Person person1= person;
System.out.println(person1);
}
}
直接赋值复制就是通过赋值 Person person1= person;本质上赋值的是对象的引用,person1和person指向的是同一对象
随着person的改变,person1也会改变
浅拷贝
复制引用但不复制引用的对象
创建一个新对象,然后将当前对象的非静态字段复制到该新对象
如果字段是值类型的,那么对该字段执行复制;如果该字段是引用类型的话,则复制引用但不复制引用的对象。
因此,原始对象及其副本引用同一个对象
每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。
要想对一个对象进行复制,就需要对clone方法覆盖
通过clone方法赋值的对象跟原来的对象时同时独立存在的,而赋值的本质上是同一个对象
浅拷贝的实现:
-
被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)
-
覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。
Object存在clone接口
重写clone接口:
package com.company.NewEntity;
public class Person implements Cloneable {
private int id;
private String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
测试类测试:
package com.company.NewEntity;
public class TestPerson {
public static void main(String[] args) throws CloneNotSupportedException {
Person person=new Person(1,"zhangsan");
Person personClone= (Person)person.clone();
System.out.println("person="+person.toString()+",personClone="+personClone.toString());
personClone.setName("lisi");
System.out.println("person="+person.toString()+",personClone="+personClone.toString());
}
}
可以看出拷贝对象与原来的对象是不同的对象
深拷贝
深拷贝不仅复制对象本身,而且复制对象包含的引用指向的所有对象。
其实就是类存在引用,引用也要实现拷贝
例:
创建一个Subject类:
package com.company.NewEntity;
public class Subject implements Cloneable {
private String subjectName;
public Subject(String subjectName) {
this.subjectName=subjectName;
}
public String getSubjectName() {
return subjectName;
}
public void setSubjectName(String subjectName) {
this.subjectName = subjectName;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
在前面的Person类中引用Subject类:
那么实现深拷贝就在于也要拷贝引用类型
Person:
package com.company.NewEntity;
public class Person implements Cloneable {
private int id;
private String name;
private Subject subject;
public Person(int id, String name, Subject subject) {
this.id = id;
this.name = name;
this.subject = subject;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", subject=" + subject +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
Person person=(Person)super.clone();//浅拷贝
person.subject= (Subject) subject.clone();//深拷贝
return person;
}
}
测试类改改:
package com.company.NewEntity;
public class TestPerson {
public static void main(String[] args) throws CloneNotSupportedException {
Subject subject=new Subject("Java");
Person person=new Person(1,"zhangsan",subject);
Person personClone= (Person)person.clone();
System.out.println("person="+person.toString()+",personClone="+personClone.toString());
personClone.setName("lisi");
System.out.println("person="+person.toString()+",personClone="+personClone.toString());
}
}
深、浅拷贝机制
通过对比深浅拷贝,可以看出区别:
浅拷贝:对象值拷贝,对于拷贝而言,拷贝出来的对象仍然保留原对象的所有引用
缺点:牵一发而动全身,只要任意一个拷贝对象(或原有对象)中的引用发生改变,所有对象均会受到影响
优点:效率高,相对于深拷贝节约空间
深拷贝:深拷贝出来的对象产生了所有引用的新的对象
缺点:深拷贝效率低,且浪费空间。
优点:修改任意一个对象,不会对其他对象产生影响
浅拷贝:
深拷贝:
深拷贝:
我们可以通过反射来检测一下成员变量、对象、类是否相同:
package com.company.NewEntity;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class TestPerson {
public static void main(String[] args) throws CloneNotSupportedException {
Subject subject=new Subject("Java");
//原Person对象
Person person=new Person(1,"zhangsan",subject);
//反射得到对象person的类、成员变量
Class personClass=person.getClass();
System.out.println(personClass.toString());
Field[] fields=personClass.getDeclaredFields();
Method[] methods=personClass.getMethods();
for(int i=0;i<fields.length;i++){
System.out.println(fields[i]);
}
System.out.println();
//拷贝对象
Person personClone= (Person)person.clone();
//获得拷贝对象cloneClass的类、成员变量、方法
Class cloneClass=personClone.getClass();
System.out.println(cloneClass.toString());
Field[] clonefields=personClass.getDeclaredFields();
Method[] clonemethods=personClass.getMethods();
for(int i=0;i<clonefields.length;i++){
System.out.println(clonefields[i]);
//对比拷贝对象成员变量和原对象变量是否相同
System.out.println("成员变量是否相同:"+(clonefields[i]==fields[i]));
}
for (int i=0;i<clonemethods.length;i++){
//对比拷贝对象方法和原对象方法
System.out.println("成员方法是否相同:"+(clonemethods[i]==methods[i]));
}
System.out.println();
//对比拷贝对象和原对象调用的类是否相同
System.out.println("class是否相同:"+(personClass==cloneClass));
//对比拷贝对象和原对象是否相同
System.out.println("Person是否相同:"+(person==personClone));
System.out.println("person="+person.toString()+",personClone="+personClone.toString());
personClone.setName("lisi");
Subject subject1=personClone.getSubject();
subject1.setSubjectName("MySql");
personClone.setSubject(subject1);
System.out.println("person="+person.toString()+",personClone="+personClone.toString());
}
}
可以看出深拷贝成员变量、对象、方法不同(有点乱),而且改变clone对象的引用变量,不会改变原对象
那么浅拷贝和深拷贝的区别呢?
将深拷贝注释掉
@Override
public Object clone() throws CloneNotSupportedException {
Person person=(Person)super.clone();//浅拷贝
/*person.subject= (Subject) subject.clone();//深拷贝*/
return person;
}
然后运行测试类:
浅拷贝其实和深拷贝机制一样,只是浅拷贝不把类中的引用拷贝
当我们设置clone对象的引用变量时,就会将前面的原对象也改变,牵一发而动全身
序列化实现深拷贝
前面JavaSE笔记(2.6)Java基础 -序列化时漏下这个知识点,到这解决
前面知道了深拷贝是在clone()方法中把引用变量也拷贝了,那如果存在多个或者嵌套引用变量,再通过clone()方法就显得繁琐
使用序列化、反序列化拷贝可以解决这个问题
例:
继续使用前面的Subject类,但改SerializableClone类来引用Subject类
Subject类继承Serializable接口
package com.company.NewEntity;
import java.io.*;
public class SerializableClone implements Serializable {
private static final long serialVersionUID=123456L;
private int id;
private Subject subject;
public SerializableClone(int id, Subject subject) {
this.id = id;
this.subject = subject;
}
public Subject getSubject() {
return subject;
}
public void setSubject(Subject subject) {
this.subject = subject;
}
@Override
public String toString() {
return "SerializableClone{" +
"id=" + id +
", subject=" + subject +
'}';
}
public static void main(String [] args){
//构造SerializableClone对象
SerializableClone clone=new SerializableClone(1,new Subject("Java"));
try {
//测试用,就不用文件保留数据
//创建字节数组输出流ByteArrayOutputStream在内存中创建一个字节数组缓冲区
ByteArrayOutputStream byteOutput=new ByteArrayOutputStream();
//创建对象的序列化输出流,并将对象保存在缓冲区
ObjectOutputStream outputStream=new ObjectOutputStream(byteOutput);
//写入流中
outputStream.writeObject(clone);
outputStream.close();
//字节数组输入流ByteArrayInputStream在内存中创建一个字节数组缓冲区
//接收字节数组数据
ByteArrayInputStream byteInput=new ByteArrayInputStream(byteOutput.toByteArray());
//创建对象的反序列化输入流
ObjectInputStream inputStream=new ObjectInputStream(byteInput);
//读出对象
SerializableClone clone1=(SerializableClone) inputStream.readObject();
System.out.println(clone1.toString());
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
使用缓冲区保存数据,将对象通过序列化、反序列化拷贝
可以通过反射检验是否为深拷贝:
//反射查看是否为深拷贝
Class scClass=clone.getClass();
Field[] fields=scClass.getDeclaredFields();
Method[] methods=scClass.getMethods();
Class cloneClass=clone1.getClass();
Field[] cloneFields=cloneClass.getDeclaredFields();
Method[] cloneMethod=cloneClass.getMethods();
System.out.println("类是否相同:"+(scClass==cloneClass));
System.out.println("对象是否相同:"+(clone==clone1));
for(int i=0;i<fields.length;i++){
System.out.println("成员变量是否相同:"+(fields[i]==cloneFields[i]));
}
for(int i=0;i<methods.length;i++){
System.out.println("方法是否相同:"+(methods[i]==cloneMethod[i]));
}
//改变拷贝对象,看是否会影响原对象
Subject subject=clone1.getSubject();
subject.setSubjectName("数据结构");
System.out.println(clone.toString()+","+clone1.toString());
序列化、反序列化产生的对象确实是拷贝的原对象
注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时
序列化不仅能保存对象,还能将对象中的引用的对象也保存,编译期就可以找出可能出现的异常,但是序列化很耗时,即各有优缺点
总结
- 复制有三种:直接赋值复制、浅拷贝、深拷贝
- 直接赋值复制是将对象的地址复制,本质上还是一个对象
- 浅拷贝会复制一个新的对象,并将基础类型变量、方法等一起复制,但是引用变量不会拷贝,而是把引用地址复制,所以会造成修改拷贝对象的引用变量,而原对象也被修改了
- 深拷贝在浅拷贝的基础上把引用对象也通过clone方法拷贝了,可以做到引用变量也与原变量不同,然后效率低且浪费空间
- 序列化、反序列化也可以实现深拷贝,通过序列化将对象保存,然后反序列化取出的对象与原对象是深拷贝。序列化完成的深拷贝优与clone的深拷贝,它会在编译期将可能的问题找出,但是序列化很耗时。
- Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等