(原创)java的拷贝方式:对象拷贝,浅拷贝,深拷贝

由赋值语句导入对象拷贝

在我们实际开发中,常常会用到简单的赋值语句=
就比如下面这个例子:

        //赋值语句 基本数据类型
        int a=3;
        int b=a;
        a=5;
        System.out.println(b);
        //赋值语句 String字符串
        String s1="s1";
        String s2=s1;
        s1="s2";
        System.out.println(s2);

打印结果如下
在这里插入图片描述
这个没什么可说的。
但是我们看下面这个例子:

class Person {
    Person son;
    String name;
    public Person(Person son, String name) {
        this.son = son;
        this.name = name;
    }
    public Person(String name) {
        this.name = name;
    }
}

上面定义了一个普通的Person类

        //对象赋值,hashCode一致
        Person son = new Person("儿子jack");
        Person p1 = new Person(son, "paul_1");
        Person p2 = p1;
//        p2=new Person(son,"paul_3");
        p2.name = "paul_2";
        p2.son=new Person("儿子seven");
        System.out.println("p1.hashCode:" + p1.hashCode());
        System.out.println("p2.hashCode:" + p2.hashCode());
        System.out.println(p1.name);
        System.out.println("p1.son.name:" + p1.son.name);

打印结果

p1.hashCode:21685669
p2.hashCode:21685669
paul_2
p2.son.name:儿子seven

这个时候我们就会有疑惑
1:为什么p1赋值给p2,修改p2内部的变量属性,会影响到p1的内部变量的属性
2:我们发现,赋值后,p1和p2的hashCode还是一样的,并没有创建一个新的对象
其实这就是所谓的对象拷贝,我们可以总结一下对象拷贝的特点:
首先我们需要知道:
Java 中的数据类型分为基本数据类型和引用数据类型。
对于这两种数据类型,在进行赋值操作、用作方法参数或返回值时,
会有值传递和引用(地址)传递的差别。

对象拷贝:
a=b
对于基本数据类型:属于值传递,修改一个不会影响另外一个
对于引用数据类型:是引用传递,修改a会影响b,修改b也会影响a。因为a和b实际是同一个内存地址的同一个对象

所以针对上面的代码,我们把注释 p2=new Person(son,“paul_3”);打开
结果就变成:

p1.hashCode:21685669
p2.hashCode:2133927002
paul_1
p1.son.name:儿子jack

这时候就正常了
因为p2其实已经指向了一个新的new出来的对象了
p2和p1指向的已经是互相没有关联的两个对象了

浅拷贝

那么如何解决刚刚上面的问题呢?
比如Person a=b;
我改动a的成员属性,不想影响到b,该如何做呢?
这就需要我们了解浅拷贝和深拷贝了
还是用一个例子来说明
我们对Person进行一个改造
让他实现Cloneable接口
然后重写他的toString和clone方法

class Person implements Cloneable{
    Person son;
    String name;

    public Person(Person son, String name) {
        this.son = son;
        this.name = name;
    }

    public Person(String name) {
        this.name = name;
    }

    /**
     * 重写clone()方法
     *
     * @return
     */
    @Override
    public Object clone() {
        //浅拷贝
        try {
            // 直接调用父类的clone()方法
            return super.clone();
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }

    @Override
    public String toString() {
        return "[Person_name: " + name + ", hashCode:" + this.hashCode() + ", son:" + son + "]";
    }
}

再看我们的测试代码:

        Person son = new Person("张三之子");
        Person p1 = new Person(son, "张三");
        Person p2 = (Person) p1.clone();
        p2.name = "李四";
        p2.son.name = "李四之子";
        System.out.println(p1);
        System.out.println(p2);

打印结果:

[Person_name: 张三, hashCode:21685669, son:[Person_name: 李四之子, hashCode:2133927002, son:null]]
[Person_name: 李四, hashCode:1836019240, son:[Person_name: 李四之子, hashCode:2133927002, son:null]]

这时候我们发现了:
1:p2修改自己的姓名,的确没有影响到p1,并且p1和p2的hashCode也是不同的
说明是两个对象
2:但是p2修改自己儿子的姓名,却影响到了p1儿子的姓名,并且p1和p2的son,
他们的hashCode都是一样的,说明是同一个对象

其实上面做的就是一个浅拷贝
步骤就是
1、实现类的Cloneable接口
2、重写类的clone方法

那么浅拷贝的特点是什么呢?
总结就是:
1:会对浅拷贝的对象本身做一个对象的复制
2:对于浅拷贝对象内的基本数据类型,还是值传递
3:对于浅拷贝对象内的引用数据类型,是值传递,就是说还是同一个对象
以上三点用这个图表示:
在这里插入图片描述
所以,浅拷贝只会复制对象本身,对于对象内部的引用数据类型的变量,是不会做拷贝的
自然修改p2的son的名字,还是会影响到p1的son的名字的
除非p2或者p1的son又指向一个新new出来的son
两者才不会互相影响

深拷贝

浅拷贝只能解决最外层对象的影响问题
如果要解决内部的互相影响问题,就需要深拷贝来处理了
首先我们对Person类做一个深拷贝的处理
(怎么处理文章后面会讲)
然后还是上面的测试代码,打印结果如下:

[Person_name: 张三, hashCode:21685669, son:[Person_name: 张三之子, hashCode:2133927002, son:null]]
[Person_name: 李四, hashCode:1836019240, son:[Person_name: 李四之子, hashCode:325040804, son:null]]

可以看到,这下问题彻底解决了
p1和p2是两个对象,p1和p2的son也是不同的两个对象了
他们的hashCode也都不一样
这就是深拷贝的特点了
1:对于基本数据类型,都是值传递,这点和浅拷贝是一样的
2:对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。
3:深拷贝相比于浅拷贝速度较慢并且花销较大。
4:为什么开销大呢?因为对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝。
具体就像下图:
在这里插入图片描述

讲完了深拷贝的特点,我们看下深拷贝的两种实现方式:
第一种,就是对象内部的每个对象都实现 Cloneable 并重写 clone() 方法,
进而实现了对象的串行层层拷贝。
比如刚刚的Person要进行深拷贝,就应该这样处理:

class Person implements Cloneable{
    Person son;
    String name;

    public Person(Person son, String name) {
        this.son = son;
        this.name = name;
    }

    public Person(String name) {
        this.name = name;
    }



    /**
     *  重写clone()方法
     * @return
     */
    @Override
    public Object clone() {
        //深拷贝
        try {
            // 直接调用父类的clone()方法
            Person person = (Person) super.clone();
            if (son!=null){
                person.son = (Person) son.clone();
            }
            return person;
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }

    @Override
    public String toString() {
        return "[Person_name: " + name + ", hashCode:" + this.hashCode() + ", son:" + son + "]";
    }
}

可以看到,对于Person内部的son,也做了拷贝处理
如果son不是Person类型的,而是其他引用类型
比如什么Woman类型
那么也要对Woman类型做这样的处理:
1、实现Cloneable接口
2、重写clone方法
可以看到,这种方式比较麻烦,写了很多重复的代码
所以我们也可以用第二种方式:
1:让Person类实现Serializable接口
2:通过字节流序列化实现深拷贝
具体代码如下:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
/**
 * 通过字节流序列化实现深拷贝,需要深拷贝的对象必须实现Serializable接口
 * 
 * @author Administrator
 */
public class CloneUtils {
	@SuppressWarnings("unchecked")
	public static <T extends Serializable> T clone(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;
	}
}

使用起来就是这样:

        Person son = new Person("张三之子");
        Person p1 = new Person(son, "张三");
        Person p2 = CloneUtils.clone(p1);
//        Person p2 = (Person) p1.clone();
        p2.name = "李四";
        p2.son.name = "李四之子";
        System.out.println(p1);
        System.out.println(p2);

最终也实现了深拷贝

总结

不同的拷贝方式有不同的特点
实际开发中,不是说我们就一定要用深拷贝或者浅拷贝
而是根据自己的实际业务需求
所以只有了解清楚了不同拷贝的特点
并且能够灵活的使用这三种拷贝方式
才算是真正的掌握了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值