JavaSE笔记(2.7)Java基础 - 复制

前言

记录学习过程

目录

  1. 概念
  2. 直接赋值复制
  3. 浅拷贝
  4. 深拷贝
  5. 深、浅拷贝机制
  6. 序列化实现深拷贝
  7. 总结

复制

概念

复制可以分为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方法赋值的对象跟原来的对象时同时独立存在的,而赋值的本质上是同一个对象

浅拷贝的实现:

  1. 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)

  2. 覆盖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方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时

序列化不仅能保存对象,还能将对象中的引用的对象也保存,编译期就可以找出可能出现的异常,但是序列化很耗时,即各有优缺点

总结

  1. 复制有三种:直接赋值复制、浅拷贝、深拷贝
  2. 直接赋值复制是将对象的地址复制,本质上还是一个对象
  3. 浅拷贝会复制一个新的对象,并将基础类型变量、方法等一起复制,但是引用变量不会拷贝,而是把引用地址复制,所以会造成修改拷贝对象的引用变量,而原对象也被修改了
  4. 深拷贝在浅拷贝的基础上把引用对象也通过clone方法拷贝了,可以做到引用变量也与原变量不同,然后效率低且浪费空间
  5. 序列化、反序列化也可以实现深拷贝,通过序列化将对象保存,然后反序列化取出的对象与原对象是深拷贝。序列化完成的深拷贝优与clone的深拷贝,它会在编译期将可能的问题找出,但是序列化很耗时。
  6. Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等
发布了49 篇原创文章 · 获赞 0 · 访问量 1229

猜你喜欢

转载自blog.csdn.net/key_768/article/details/104262977