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

1为什么要克隆?

克隆的对象可能已经包含一些已经修改过的属性,保留着你想克隆对象的值,而new出来的对象的属性全是一个新的对象,对应的属性没有值,所以我们还要重新给这个对象赋值。

即当需要一个新的对象来保存当前对象的“状态”就靠clone方法了,那么我把这个对象的临时属性一个一个的赋值给我新new的对象不也行嘛?可以是可以,但是一来麻烦不说,二来,大家通过上面的源码都发现了clone是一个native方法,就是快啊,在底层实现的。

克隆究竟是什么?

先来回忆为一个包含对象引用的变量建立副本时会发生什么 。原变量和副本都是同一个对象的引用 ( 见图 6 - 1 )。这说明 ,任何一个变量改变都会影响另一个变量。

Employee original = new Employee ( " John Public " , 50000 ) ;
Employee copy = original ;
copy . raiseSalary ( lO ) ; // oops - also changed original

上述 original和 copy两个对象引用指向的是同一个对象

如果希望 copy 是一个新对象, 它的初始状态与 original相同 , 但是之后它们各自会有自
己不同的状态 , 这种情况下就可以使用 clone 方法。

Employee copy = original.clone() ;
copy . raiseSalary ( lO ) ; / / OK original unchanged

在这里插入图片描述

不过并没有这么简单。clone方法是Object的一个protected方法,这说明你的代码不能直接调用这个方法。只有Employee类可以克隆Employee对象。这个限制是有原因的。想想看Object类如何实现clone。它对于这个对象一无所知,所以只能逐个域的拷贝地进行拷贝。如果对象中所有的数据域或者其他基本类型,拷贝这些域没有任何问题,但是如果对象包含子对象的引用,拷贝域就会得到相同子对象的另一个引用,这样一来原对象和克隆的对象仍然会共享一些信息

为了更直观地说明这个问题, 考虑第4 章介绍过的 Employee 类。 图 6 - 2 显示了使用Object 类的 clone 方法克隆这样一个 Employee 对象会发生什么 。 可以看到 , 默认的克隆操作是 “ 浅拷贝 ” , 并没有克隆对象中引用的其他对象。( 这个图显示了一个共享的 Date 对象。 出于某种原因 ( 稍后就会解释这个原因 ), 这个例子使用了 Employee 类的老版本 , 其中的雇用日期仍用 Date 表示 。

2浅拷贝的潜在危害

浅拷贝会有什么问题吗?

这要看具体情况。

如果原对象和浅克隆对象共享的子对象是不可变的 , 那么这种共享就是安全的

如果子对象属于一个不可变的类,如String,就是这种情况。或者在对象的生命期中,子对象一直包含不变的常量,没有更改器方法会改变它,也没有方法会生成它的作用,这种情况下同样是安全的。

在这里插入图片描述

不过,通常子对象都是可变的,必须重新定义clone方法来建立一个浅拷贝,同时克隆所有子对象。 在这个例子中 , hireDay域是一个 Date , 这是可变的,所以它也需要克隆 。( 出于这个原因 , 这个例子使用 Date 类型的域而不是 LocalDate 来展示克隆过程。 如果 hireDay是不可变的 LocalDate 类的一个实例,就无需我们做任何处理了 )

对于每一个类,需要确定:

1.默认的clone方法是否满足要求

2.是否可以在可变的子对象上调用clone来修补默认的clone方法

3.是否不该使用clone方法

3如何实现拷贝呢?

实际上第 3 个选项是默认选项。 如果选择第1 项或第 2 项 , 类必须 :
1 ) 实现 Cloneable 接口 ;
2 ) 重新定义 clone 方法 , 并指定 public 访问修饰符 。

4那么如何做到深拷贝呢?

很简单就是在在实现Clonable接口之后,自定义的clone方法中,调用引用对象的clone方法,这样就避免了Object.clone方法默认的对于引用对象的浅拷贝

看个例子很快就懂了:

package com.interview.xsj.cloneexample;

import java.util.Date;
import java.util.GregorianCalendar;

/**
 * @author xsj
 * @create 2021-07-13 17:31
 */
public class CloneTest {
    public static void main(String[] args) {
        try{
            Employee original = new Employee("John Q. Public", 50000);
            original.setHireDay(2000, 1, 1);
            Employee copy = original.clone();
            copy.raiseSalary(10);
            copy.setHireDay(2002, 12, 31);
            System.out.println("original=" + original);
            System.out.println("copy=" + copy);
        }catch (CloneNotSupportedException e){
            e.printStackTrace();
        }
    }
}

class Employee implements Cloneable{
    private String name;
    private double salary;
    private Date hireDay;
    public Employee(String n, double s)
    {
        name = n;
        salary = s;
        hireDay = new Date();
    }

    public Employee clone()throws CloneNotSupportedException{
        Employee cloned=(Employee)super.clone();

        cloned.hireDay=(Date)hireDay.clone();
        return cloned;
    }

    /**
     * Set the hire day to a given date.
     * @param year the year of the hire day
     * @param month the month of the hire day
     * @param day the day of the hire day
     */
    public void setHireDay(int year, int month, int day)
    {
        Date newHireDay = new GregorianCalendar(year, month - 1, day).getTime();

        // Example of instance field mutation
        hireDay.setTime(newHireDay.getTime());
    }

    public void raiseSalary(double byPercent)
    {
        double raise = salary * byPercent / 100;
        salary += raise;
    }

    public String toString()
    {
        return "Employee[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]";
    }
}

核心就在于clone方法中的这一句

在这里插入图片描述

从结果可以看到引用对象hireDay修改了

在这里插入图片描述

5深拷贝与浅拷贝总结

两者的区别:就是关于需要clone的对象中如果包含引用对象时,是否将引用对象也进行一个clone(重新在堆空间中创建一个对象)

  1. 浅拷⻉:对基本数据类型进⾏值传递,对引⽤数据类型进⾏引⽤传递般的拷⻉,此为浅拷⻉。(仅仅拷贝对象本身,而不拷贝对象包含的引用指向的对象)
  2. 深拷⻉:对基本数据类型进⾏值传递,对引⽤数据类型,创建⼀个新的对象,并复制其内容,此为深拷⻉。(不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象)

在这里插入图片描述

参考:Java核心技术卷 一

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值