美文网首页Java 杂谈Java技术分享
什么是对象克隆(拷贝/复制)?浅克隆与深克隆有什么区别?

什么是对象克隆(拷贝/复制)?浅克隆与深克隆有什么区别?

作者: Belmode | 来源:发表于2018-10-09 12:16 被阅读1次

一、什么是对象克隆?

首先我们需要知道,什么是对象的克隆,或者说复制。一个业务逻辑,需要一个新的对象,但是类型和值都是之前的,也就是说,新状态和之前完全一样。使用new和赋值语句或者set注入都是可以的,但是,这会花费大量开销去做,效率低,并且还会产生冗余代码。
恰好java语言本身契合了原型设计模式,给我们提供了一个clone方法在Object对象中,只要需要克隆的对象实现Cloneable接口,那么我们只要简单的调用一下该方法就可以获得表面完全不同的对象。
例1:

public class User implements Cloneable{
    @Override
    protected Object clone() throws CloneNotSupportedException {
        // TODO Auto-generated method stub
        return super.clone();
    }
}
......
User u = new User();
User uCopy = u.clone();
//显然u和uCopy已经是两个不同的对象了
System.out.println(u == uCopy);//false

二、如何实现对象克隆

上文也是提到,克隆对象基本只要两步:

  1. 实现Cloneable接口
  2. 实现clone()方法,并调用父类clone()

需要注意,Objectclone()方法是在java平台层实现的native方法,具有开销小,速度快的特点。而且,原始的Object方法是被protected修饰的,在这里需要修改为public,如果不这么做,浅克隆时没有问题,深克隆就会遇到权限不够的问题。java继承还有个原则,就是子类覆写父类方法,访问修饰符权限不能低于父类。

三、什么是浅克隆和深克隆

例2:

/**
 * 浅克隆/浅拷贝
 * @datetime  2018-10-09 09:43:22
 * @author Belmode
 *
 */
public class ShallowClone {
    public static void main(String[] args) throws Exception {
        Person p = new Person("李达康", "55", "汉东省京州市");
        Person p2 = (Person) p.clone();
        //一级对象
        System.out.println(p == p2);//false
        //二级对象
        System.out.println(p.personalInfo == p2.personalInfo);//true
        //三级对象
        System.out.println(p.personalInfo.name == p2.personalInfo.name);//true
    }
}
class PersonalInfo implements Cloneable{
    String name;
    String age;
    String address;
    public PersonalInfo(String name, String age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
class Person implements Cloneable{
    PersonalInfo personalInfo;
    
    public Person(String name, String age, String address) {
        this.personalInfo = new PersonalInfo(name, age, address);
    }
    
    @Override
    public Object clone() throws CloneNotSupportedException {
         return super.clone();
    }
}

在本例中,Person类拥有PersonalInfo类作为属性,两者都实现了Cloneable接口,并且重写了clone()方法。
ShallowClone类的main方法中,构建了一个Person对象p,调用clone()方法,产生p2pp2不是一个对象,但是pp2内部的personalInfo属性是同一对象,这就会导致一个致命的问题,当我修改p.personalInfo内部的属性值,结果p2.personalInfo也被修改了。
p.personalInfo.name = "孙正义";
System.out.println(p2.personalInfo.name); //孙正义

  • 这是什么原因导致的?
    这是因为java中不仅仅有基本变量,还有引用变量
    浅拷贝:仅仅克隆基本类型变量,而不克隆引用类型的变量
    深克隆:既克隆基本类型变量,也克隆引用类型变量

  • 其实本来原生的clone()方法也是,复制了引用的,但是为什么引用类型还是同一个呢?
    就是因为java中,只有值传递,没有引用传递,引用在内存中还是值,是地址的hash值,一个特殊标记唯一堆内存块的值。
    图1:

在JVM虚拟机中,事实上就是一个java.exe或者javaw.exe进程里, 会存在栈内存和堆内存,堆内存又可细分,这里就不多说了,想深入了解的可以问度娘。在方法区中存储着PersonPersonalInfo的类模板,通过new 派生出 ppesonalInfo的实例,如图中箭头所示。此时p里引用着personalInfo实例(其实应该从p实例里的方框画箭头到栈位置的personalInfo标签,这里为了下面说明,就没那么做了)
在栈内存中存储的都是hashcode,类似于eb5238b,存在p的位置,就产生了图中的箭头

图2:调用clone()方法,克隆出p2


如图所示,p2是真的新实例,但是personalInfo也是复制的值,p2里的personalInfo属性保存personalInfo实例的地址值,所以,如黄线所示,还是引用到原本的personalInfo实例。
因此,修改p.personalInfo里的属性,p2.personalInfo里的值也会收到影响!!

那么,如果我修改p.personalInfo属性,p.personalInfo = new PersonalInfo.....p2.pernalInfo会不会收到影响呢?
答案是不会的。
图3:

如图所示,会断开红叉处的连接,生成橙色连接。如橙线所示。
p对象和p2personalInfo属性其实已经不同了。

话题有点跑偏了,因为会出现上面的原因,所以需要深克隆。

例3:

public class DeepClone {
    public static void main(String[] args) throws Exception {
        Person2 person = new Person2("李达康", "55", "汉东省京州市");
        Person2 person2 = (Person2) person.clone();
        //一级对象
        System.out.println(person == person2);//false
        //二级对象
        System.out.println(person.personalInfo == person2.personalInfo);//false
        //三级对象
        System.out.println(person.personalInfo.name == person2.personalInfo.name);//false
    }
}

class PersonalInfo2 implements Cloneable{
    String name;
    String age;
    String address;
    public PersonalInfo2(String name, String age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
    
    @Override
    public Object clone() throws CloneNotSupportedException {
        PersonalInfo2 pInfo = (PersonalInfo2) super.clone();
        pInfo.name = new String(this.name);
        pInfo.age = new String(this.age);
        pInfo.address = new String(this.address);
        return pInfo;
    }
}
class Person2 implements Cloneable{
    PersonalInfo2 personalInfo;
    
    public Person2(String name, String age, String address) {
        this.personalInfo = new PersonalInfo2(name, age, address);
    }
    
    @Override
    public Object clone() throws CloneNotSupportedException {
       Person2 p = (Person2) super.clone();
       p.personalInfo = (PersonalInfo2) this.personalInfo.clone();
       return p;
    }
}

在这里,Person2调用父类clone(),克隆出新实例p,也给p.personalInfo调用 PersonalInfo2的父类方法,而在此中

    @Override
    public Object clone() throws CloneNotSupportedException {
        PersonalInfo2 pInfo = (PersonalInfo2) super.clone();
        pInfo.name = new String(this.name);
        pInfo.age = new String(this.age);
        pInfo.address = new String(this.address);
        return pInfo;
    }

对所有引用类型String全部new产生和之前不同的对象,达到深度克隆的目的。
(因为String没有实现Cloneable接口,需要通过new派生,这里是举特殊例子。如果是实现Cloneable接口的类,直接嵌套调用clone()方法就好了。)
这样,新的Person2实例,与之前的引用完全不同,但是值是一样的!深度克隆成功。

        Person2 person = new Person2("李达康", "55", "汉东省京州市");
        Person2 person2 = (Person2) person.clone();
        //一级对象
        System.out.println(person == person2);//false
        //二级对象
        System.out.println(person.personalInfo == person2.personalInfo);//false
        //三级对象
        System.out.println(person.personalInfo.name == person2.personalInfo.name);//false

        System.out.println(person.personalInfo.name);//李达康
        System.out.println(person2.personalInfo.name);//李达康

三、还有其他深度克隆方式吗?

对象序列化与反序列化。但是这么做开销更大,不建议如此。(这里不做举例)

总结:

1.浅克隆:只复制基本类型的数据,引用类型的数据只复制了引用的地址,引用的对象并没有复制,在新的对象中修改引用类型的数据会影响原对象中的引用。直接使用clone方法,再嵌套的还是浅克隆,因为有些引用类型不能直接克隆。
2.深克隆:是在引用类型的类中也实现了clone,是clone的嵌套,并且在clone方法中又对没有clone方法的引用类型又做差异化复制,克隆后的对象与原对象之间完全不会影响,但是内容完全相同。
3.使用序列化也能完成深复制的功能:对象序列化后写入流中,此时也就不存在引用什么的概念了,再从流中读取,生成新的对象,新对象和原对象之间也是完全互不影响的。

相关文章

  • java克隆

    java克隆 java克隆分为浅克隆和深克隆,概念如下: 浅拷贝(浅克隆)克隆出来的对象的所有变量都含有与原来的对...

  • Java深复制浅复制解析.md

    Java 克隆概念 Java克隆分为深克隆和浅克隆两种类型。 浅复制(浅克隆)被复制对象的所有变量都含有与原来的对...

  • 什么是对象克隆(拷贝/复制)?浅克隆与深克隆有什么区别?

    一、什么是对象克隆? 首先我们需要知道,什么是对象的克隆,或者说复制。一个业务逻辑,需要一个新的对象,但是类型和值...

  • Object的克隆

    克隆一个对象 浅克隆 浅克隆(也叫做浅拷贝)仅仅复制了这个对象本身的成员变量,该对象如果引用了其他对象的话,也不对...

  • js浅克隆与深克隆

    什么是深克隆,什么是浅克隆呢? 首先,克隆只针对对象、数组、函数等复杂数据。浅克隆就是将栈内存中的引用复制一份,赋...

  • Java基础 - 深拷贝和浅拷贝

    Java 的深拷贝和浅拷贝 什么是深拷贝、浅拷贝 (深克隆、浅克隆)? 在 Java 中,数据类型分为 基本数据类...

  • 深克隆--何时是尽头?

    As we all know Java有两种拷贝,浅拷贝和深拷贝,高大上的叫法也叫浅克隆和深克隆。 深克隆有时会碰...

  • 原型模式

    原型模式就是将对象复制一份,分为浅克隆和深克隆 一、要点 1、必须实现Cloneable接口才能实现克隆2、浅克隆...

  • Java-原型模式

    模式定义: 使用场景: 以克隆层次来区分,可有深克隆和浅克隆两种。 浅克隆实现如下: 运行结果: 浅克隆就是拷贝一...

  • Java 之浅拷贝、深拷贝,你到底知多少?

    在 Java 开发中,对象拷贝或者说对象克隆是常有的事,对象克隆最终都离不开直接赋值、浅拷贝、深拷贝 这三种方式,...

网友评论

    本文标题:什么是对象克隆(拷贝/复制)?浅克隆与深克隆有什么区别?

    本文链接:https://www.haomeiwen.com/subject/ruqeaftx.html