java中的浅拷贝与深拷贝详解

版权声明:总结不易,希望转发时可以带上原文地址,万分感谢! https://blog.csdn.net/baidu_38609744/article/details/82219563

本文将由浅入深的讲解下作者学到的java中的拷贝知识点,其中的遇到的一些坑和重要知识点也会一一点出。作者菜鸡一头,如有错误,非常感谢您的指正!

########################################################################

如果我们想实现一个对象的拷贝,那么不涉及JDK提供的copy类最简单的我们可以想到的是再次new出来一个这个类的实例,然后调用get和set方法去进行赋值,但是这样即使自定义抽取拷贝方法代码重用度也是极低的,只能适用于本项目。

其实JDK已经为我们提供好了对于对象的克隆方法。我们可以通过查看API文档中Object类的介绍或者通过在IDE中Ctrl+鼠标左键点进Object类查看,我们会发现有一个clone的native修饰方法(native修饰的java方法为JNI本地方法接口,一般都是C语言实现),又由于所有类都是继承于Object类的,所以每个类都是有这个方法的。

但是我们会发现这个方法是protected修饰的,这样根据下表我们可以得出protected的访问范围,这就意味着我们想要调用这个方法就必须去重写这个方法。

public protected default private
同一个类
同一个包 X
子类 X X
不同包 X X X

并且根据JDK规范,想要调用clone方法来实现克隆就必须所要进行clone的类实现Cloneable接口,否则调用clone方法时会报java.lang.CloneNotSupportedException异常

那么下面将新建一个房屋类和一个窗户的类,然后通过这房屋类去重写clone方法来实现对房屋对象进行拷贝:

package cn.xb.entity;

import java.util.Date;

/**
 * 房屋
 * @author binxu
 *
 */
public class House implements Cloneable {
    //房子颜色
    private String color;
    //房子高度
    private Integer height;
    //房子拥有着姓名
    private String owner;
    //窗户
    private Window window;
    //建造日期
    private Date manufactureDate;

    public Date getManufactureDate() {
        return manufactureDate;
    }
    public void setManufactureDate(Date manufactureDate) {
        this.manufactureDate = manufactureDate;
    }
    public Window getWindow() {
        return window;
    }
    public void setWindow(Window window) {
        this.window = window;
    }
    public String getColor() {
        return color;
    }
    @Override
    public String toString() {
        return "House [color=" + color + ", height=" + height + ", owner=" + owner + ", window=" + window
                + ", manufactureDate=" + manufactureDate + "]";
    }
    public void setColor(String color) {
        this.color = color;
    }
    public Integer getHeight() {
        return height;
    }
    public void setHeight(Integer height) {
        this.height = height;
    }
    public String getOwner() {
        return owner;
    }
    public void setOwner(String owner) {
        this.owner = owner;
    }
    /**
     * 这里要注意,Object类中的clone方法为protected修饰,我们想要在非本包调用就必须将protected修饰符改为public,这样满足重写规范
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

}
package cn.xb.entity;
/**
 * 窗户
 * @author binxu
 *
 */
public class Window {
    //品牌
    private String pinpai;
    //是否拉伸型
    private Boolean isStretch;
    public String getPinpai() {
        return pinpai;
    }
    public void setPinpai(String pinpai) {
        this.pinpai = pinpai;
    }
    public Boolean getIsStretch() {
        return isStretch;
    }
    public void setIsStretch(Boolean isStretch) {
        this.isStretch = isStretch;
    }
    @Override
    public String toString() {
        return "Window [pinpai=" + pinpai + ", isStretch=" + isStretch + "]";
    }

}

测试类

package cn.xb;

import java.util.Date;

import cn.xb.entity.House;
import cn.xb.entity.Window;
/**
 * 测试类
 * @author binxu
 *
 */
public class MainApplication {
    public static void main(String[] args) {
        //new出来一栋房子
        House house = new House();
        //设置房子颜色、高度、拥有者姓名
        house.setColor("红色");
        house.setHeight(8);
        house.setOwner("张三");
        house.setManufactureDate(new Date());
        Window window = new Window();
        window.setIsStretch(false);
        window.setPinpai("LOL牌");
        house.setWindow(window);
        System.out.println("原对象:" + house + ",精确时间为:" + house.getManufactureDate().getTime());

        try {
            House clone = (House) house.clone();
            System.out.println("克隆后的对象是否与原对象是在内存中是同一个:" + (clone == house));
            System.out.println("克隆后的对象" + clone + ",精确时间为:" + clone.getManufactureDate().getTime());
            System.out.println("克隆后的房屋的窗户是否和原对象窗户在内存中为同一个" + (clone.getWindow() == house.getWindow()));
            System.out.println("克隆后的房屋的窗户是否和原对象生产日期在内存中为同一个" + (clone.getManufactureDate() == house.getManufactureDate()));
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }

}

代码输出:


**原对象:House [color=红色, height=8, owner=张三, window=Window [pinpai=LOL牌, isStretch=false], manufactureDate=Thu Aug 30 11:54:30 CST 2018],精确时间为:1535601270442
**克隆后的对象是否与原对象是在内存中是同一个:false
**克隆后的对象House [color=红色, height=8, owner=张三, window=Window [pinpai=LOL牌, isStretch=false], manufactureDate=Thu Aug 30 11:54:30 CST 2018],精确时间为:1535601270442
**克隆后的房屋的窗户是否和原对象窗户在内存中为同一个true
**克隆后的房屋的窗户是否和原对象生产日期在内存中为同一个true

通过输出我们会发现虽然克隆后的房屋在内存中与原房屋在内存中已经不是同一块,但是房屋类中的窗户window引用和生产日期manufacture仍然为原房屋对象中的窗户和日期引用,这样我们去修改克隆后的对象或者原对象的窗户属性或者日期时,对应的另一个对象也会被影响,这样暴露的问题是我们仅克隆了最外层的房屋,但是其内部的引用属性仍然被原封不动的复制到克隆对象了

因此修改代码让window窗户类也实现Cloneable接口,并且将房屋的clone方法改造下,不再是单一的调用JDK原封不动的clone方法,而是修改逻辑让其内部的window和manufacture也被克隆一份到克隆对象中:

package cn.xb.entity;
/**
 * 窗户
 * @author binxu
 *
 */
public class Window implements Cloneable {
    //品牌
    private String pinpai;
    //是否拉伸型
    private Boolean isStretch;
    public String getPinpai() {
        return pinpai;
    }
    public void setPinpai(String pinpai) {
        this.pinpai = pinpai;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    public Boolean getIsStretch() {
        return isStretch;
    }
    public void setIsStretch(Boolean isStretch) {
        this.isStretch = isStretch;
    }
    @Override
    public String toString() {
        return "Window [pinpai=" + pinpai + ", isStretch=" + isStretch + "]";
    }

}
package cn.xb.entity;

import java.util.Date;

/**
 * 房屋
 * @author binxu
 *
 */
public class House implements Cloneable {
    //房子颜色
    private String color;
    //房子高度
    private Integer height;
    //房子拥有着姓名
    private String owner;
    //窗户
    private Window window;
    //建造日期
    private Date manufactureDate;

    public Date getManufactureDate() {
        return manufactureDate;
    }
    public void setManufactureDate(Date manufactureDate) {
        this.manufactureDate = manufactureDate;
    }
    public Window getWindow() {
        return window;
    }
    public void setWindow(Window window) {
        this.window = window;
    }
    public String getColor() {
        return color;
    }
    @Override
    public String toString() {
        return "House [color=" + color + ", height=" + height + ", owner=" + owner + ", window=" + window
                + ", manufactureDate=" + manufactureDate + "]";
    }
    public void setColor(String color) {
        this.color = color;
    }
    public Integer getHeight() {
        return height;
    }
    public void setHeight(Integer height) {
        this.height = height;
    }
    public String getOwner() {
        return owner;
    }
    public void setOwner(String owner) {
        this.owner = owner;
    }
    /**
     * 这里要注意,Object类中的clone方法为protected修饰,我们想要在非本包调用就必须将protected修饰符改为public,这样满足重写规范
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        //思路:先获取到通过JDK原方法克隆得到的房屋克隆对象,然后将其带过来的生产日期manufactureDate和窗户window替换为对应的clone对象
        //这样每一个引用都调用各自的clone方法,获取到克隆对象,最后整合到最外层的house对象中去
        House clone = (House) super.clone();
        clone.setWindow((Window)this.window.clone());
        clone.setManufactureDate((Date)this.manufactureDate.clone());
        return clone;
    }

}

测试类

package cn.xb;

import java.util.Date;

import cn.xb.entity.House;
import cn.xb.entity.Window;
/**
 * 测试类
 * @author binxu
 *
 */
public class MainApplication {
    public static void main(String[] args) {
        //new出来一栋房子
        House house = new House();
        //设置房子颜色、高度、拥有者姓名
        house.setColor("红色");
        house.setHeight(8);
        house.setOwner("张三");
        house.setManufactureDate(new Date());
        Window window = new Window();
        window.setIsStretch(false);
        window.setPinpai("LOL牌");
        house.setWindow(window);
        System.out.println("原对象:" + house + ",精确时间为:" + house.getManufactureDate().getTime());

        try {
            House clone = (House) house.clone();
            System.out.println("克隆后的对象是否与原对象是在内存中是同一个:" + (clone == house));
            System.out.println("克隆后的对象" + clone + ",精确时间为:" + clone.getManufactureDate().getTime());
            System.out.println("克隆后的房屋的窗户是否和原对象窗户在内存中为同一个" + (clone.getWindow() == house.getWindow()));
            System.out.println("克隆后的房屋的窗户是否和原对象生产日期在内存中为同一个" + (clone.getManufactureDate() == house.getManufactureDate()));
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }

}

输出:


原对象:House [color=红色, height=8, owner=张三, window=Window [pinpai=LOL牌, isStretch=false], manufactureDate=Thu Aug 30 12:09:12 CST 2018],精确时间为:1535602152536
克隆后的对象是否与原对象是在内存中是同一个:false
克隆后的对象House [color=红色, height=8, owner=张三, window=Window [pinpai=LOL牌, isStretch=false], manufactureDate=Thu Aug 30 12:09:12 CST 2018],精确时间为:1535602152536
克隆后的房屋的窗户是否和原对象窗户在内存中为同一个false
克隆后的房屋的窗户是否和原对象生产日期在内存中为同一个false

这样就解决了原对象的深度克隆的需求,但是这样还没有结束,我么会发现一个问题,房屋类中有窗户和日期两个引用,那么我们就要在clone中添加两段代码去分别获取到窗户和日期的clone对象,然后整合到房屋对象,那么假如一个对象有1000个10000个引用属性那?哈哈,有点夸张!或者一个类中的引用属性不确定,需要经常变动,那么每次变动就都要修改代码!

因此这里我们在推荐一种使用字节流数组操作的方法,改变原来的思路,以一种拷贝文件的形式来实现深度拷贝。不管你几个引用属性,我都一次搞定!

这里我有一点想法,如有错误,非常感谢你的指正:对于计算机内存储的任何东西,一个java文件是你可以直接看到他后缀名为java的一个文件,一个运行时的数组你看不到,但是我觉得对于计算机来说,这两者都是一样的,都是0和1,那么我们以拷贝文件的思路来拷贝对象,也就顺理成章了,因为对计算机而言,都是拷贝0和1

修改以上房屋、窗户、和测试类,上代码:

package cn.xb.serentity;

import java.io.Serializable;
import java.util.Date;

/**
 * 房屋
 * @author binxu
 *
 */
public class House implements Serializable {
    //房子颜色
    private String color;
    //房子高度
    private Integer height;
    //房子拥有着姓名
    private String owner;
    //窗户
    private Window window;
    //建造日期
    private Date manufactureDate;

    public Date getManufactureDate() {
        return manufactureDate;
    }
    public void setManufactureDate(Date manufactureDate) {
        this.manufactureDate = manufactureDate;
    }
    public Window getWindow() {
        return window;
    }
    public void setWindow(Window window) {
        this.window = window;
    }
    public String getColor() {
        return color;
    }
    @Override
    public String toString() {
        return "House [color=" + color + ", height=" + height + ", owner=" + owner + ", window=" + window
                + ", manufactureDate=" + manufactureDate + "]";
    }
    public void setColor(String color) {
        this.color = color;
    }
    public Integer getHeight() {
        return height;
    }
    public void setHeight(Integer height) {
        this.height = height;
    }
    public String getOwner() {
        return owner;
    }
    public void setOwner(String owner) {
        this.owner = owner;
    }

}
package cn.xb.serentity;

import java.io.Serializable;

/**
 * 窗户
 * @author binxu
 *
 */
public class Window implements Serializable {
    //品牌
    private String pinpai;
    //是否拉伸型
    private Boolean isStretch;
    public String getPinpai() {
        return pinpai;
    }
    public void setPinpai(String pinpai) {
        this.pinpai = pinpai;
    }
    public Boolean getIsStretch() {
        return isStretch;
    }
    public void setIsStretch(Boolean isStretch) {
        this.isStretch = isStretch;
    }
    @Override
    public String toString() {
        return "Window [pinpai=" + pinpai + ", isStretch=" + isStretch + "]";
    }

}

一个重点!!既然是通过流的方式来拷贝,必须锁要拷贝到类实现Serializable接口,否则报如下异常: java.io.NotSerializableException

上测试类:

package cn.xb;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;

import cn.xb.serentity.House;
import cn.xb.serentity.Window;

public class MainApplication1 {
    public Object copyObject(Object object) throws IOException, ClassNotFoundException {
        //创建字节数组输出流将索要拷贝对象写入
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        //创建对象输出流将字节数组输出流传入直接将对象写入字节输出流
        ObjectOutputStream objectOutputStrea = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStrea.writeObject(object);
        //将刚写入的输出流转化为字节数组传入字节数组输入流
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        //对象输入流包装读取为一个对象
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        return objectInputStream.readObject();
    }
    public static void main(String[] args) throws ClassNotFoundException, IOException {
        //new出来一栋房子
        House house = new House();
        //设置房子颜色、高度、拥有者姓名
        house.setColor("红色");
        house.setHeight(8);
        house.setOwner("张三");
        house.setManufactureDate(new Date());
        Window window = new Window();
        window.setIsStretch(false);
        window.setPinpai("LOL牌");
        house.setWindow(window);
        System.out.println("原对象:" + house + ",精确时间为:" + house.getManufactureDate().getTime());
        //开始克隆
        MainApplication1 application1 = new MainApplication1();
        House clone = (House) application1.copyObject(house);
        System.out.println("克隆后的对象是否与原对象是在内存中是同一个:" + (clone == house));
        System.out.println("克隆后的对象" + clone + ",精确时间为:" + clone.getManufactureDate().getTime());
        System.out.println("克隆后的房屋的窗户是否和原对象窗户在内存中为同一个" + (clone.getWindow() == house.getWindow()));
        System.out.println("克隆后的房屋的窗户是否和原对象生产日期在内存中为同一个" + (clone.getManufactureDate() == house.getManufactureDate()));
    }
}

输出结果

原对象:House [color=红色, height=8, owner=张三, window=Window [pinpai=LOL牌, isStretch=false], manufactureDate=Thu Aug 30 12:36:16 CST 2018],精确时间为:1535603776580
克隆后的对象是否与原对象是在内存中是同一个:false
克隆后的对象House [color=红色, height=8, owner=张三, window=Window [pinpai=LOL牌, isStretch=false], manufactureDate=Thu Aug 30 12:36:16 CST 2018],精确时间为:1535603776580
克隆后的房屋的窗户是否和原对象窗户在内存中为同一个false
克隆后的房屋的窗户是否和原对象生产日期在内存中为同一个false

这样就完成了使用字节数组流来实现深拷贝的过程。最后还是想说如有错误,非常感谢您的指正,您的指正可以让我学到更多的东西!如果有知识点上的讨论,更是非常荣幸与开心!!

猜你喜欢

转载自blog.csdn.net/baidu_38609744/article/details/82219563