面试题——浅克隆深克隆

本文深入探讨了浅克隆和深克隆的概念,详细讲解了JAVA实现克隆的条件及Object.clone()的特点,并通过多种方式展示了如何实现深克隆,包括自定义克隆方法、构造方法、字节流、第三方工具及JSON工具类。

类的成员变量分为值类型和引用类型。

浅克隆

浅克隆是将原型对象的成员变量为值类型复制一份给克隆对象,而成员变量为引用对象的引用地址复制给克隆对象,说白了就是共享引用对象。
在这里插入图片描述

深克隆

深克隆就是原型对象不管成员变量是什么类型都要复制一份克隆对象,原型对象和克隆对象是两个完全独立的对象。
在这里插入图片描述

JAVA实现克隆条件

1、实现Cloneable接口。
2、重写Ojbect类中clone()方法。

Object.clone()几点特点

1、对于所有对象来说,x.clone() !=x 应当返回 true,因为克隆对象与原对象不是同一个对象;
2、对于所有对象来说,x.clone().getClass() == x.getClass() 应当返回 true,因为克隆对象与原对象的类型是一样的;
3、对于所有对象来说,x.clone().equals(x) 应当返回 true,因为使用 equals 比较时,它们的值都是相同的。

Arrays.copyOf()的克隆方式

@Data
public class Student {
    private int id;
    private String name;

    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public static void main(String[] args) {
        Student[] students = {new Student(1,"哈哈哈")};
        Student[] students1 = Arrays.copyOf(students,students.length);
        students1[0].setId(2);
        System.out.println("原型对象:"  +  students[0].getId());
        System.out.println("克隆对象:"  + students1[0].getId() );
    }
}

在这里插入图片描述
结果显示,修改克隆对象的值,原型对象的值也随着改变。

数组属于比较特殊的引用类型, Arrays.copyOf()只是把引用地址复制了一份给克隆对象。 Arrays.copyOf()属于浅克隆。资源共享。

浅克隆的实现

Object的clone()可以直接实现浅克隆。

深克隆的实现

深克隆可以有很多种方式进行实现:

  • 所有对象都实现克隆方法;

  • 通过构造方法实现深克隆;

  • 使用 JDK 自带的字节流实现深克隆;

  • 使用第三方工具实现深克隆,比如 Apache Commons Lang;

  • 使用 JSON 工具类实现深克隆,比如 Gson、FastJSON 等。

1、所有对象都实现克隆方法;
import lombok.Data;

public class CloneExample1 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address(1,"广州");
        People people = new People(1,"Java", address);
        //  克隆
        People people1 = people.clone();
        //  修改原型
        people1.address.setCity("深圳");
        //  输出比对结果
        System.out.println("原型对象:" + people.address.getCity());
        System.out.println("克隆对象:" + people1.address.getCity());
    }


    //  用户类
    @Data
    static class People implements Cloneable{
        private Integer id;
        private String name;
        private Address address;

        public People(Integer id, String name, Address address) {
            this.id = id;
            this.name = name;
            this.address = address;
        }

        @Override
        protected People clone() throws CloneNotSupportedException {
            People people = (People)super.clone();
            System.out.println(this.address.clone());
            people.setAddress(this.address.clone());
            return people;
        }
    }


    //  地址类
    @Data
    static class Address implements Cloneable{
        private Integer id;
        private String city;
        public Address(Integer id, String city) {
            this.id = id;
            this.city = city;
        }

        @Override
        protected Address clone() throws CloneNotSupportedException {
            return (Address)super.clone();
        }
    }

}

在这里插入图片描述
让所有引用对象都是实现克隆,从而实现克隆对象实现深克隆。

2、通过构造方法实现深克隆;
 // 调用构造函数克隆对象
        People p2 = new People(p1.getId(), p1.getName(),
                new Address(p1.getAddress().getId(), p1.getAddress().getCity()));

创建克隆对象的时候直接赋值,是一种笨方法。

3、使用 JDK 自带的字节流实现深克隆;
import lombok.Data;
import java.io.*;

public class CloneExample2 {

    public static void main(String[] args) {
        Address address = new Address(1,"广州");
        People people1 = new People(1,"Java", address);
        //  通过字节流实现克隆
        People people2 = (People) StreamClone.clone(people1);
        //  修改原型
        people1.getAddress().setCity("深圳");
        //  输出比对结果
        System.out.println("原型对象:" + people1.address.getCity());
        System.out.println("克隆对象:" + people2.address.getCity());
    }


    static class StreamClone{
        /**
          * 通过字节流实现克隆
          */
        public static <T extends Serializable > T clone(People people) {
            T cloneObj = null;
            try {
                ByteArrayOutputStream bo = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(bo);
                oos.writeObject(people);
                oos.close();
                //  分配内存,写入原型对象,生成新对象
                ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
                ObjectInputStream oi = new ObjectInputStream(bi);
                //  返回生成的新对象
                cloneObj = (T) oi.readObject();
                oi.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return cloneObj;
        }
    }
    //  用户类
    @Data
    static class People implements Serializable{
        private Integer id;
        private String name;
        private Address address;
        public People(Integer id, String name, Address address) {
            this.id = id;
            this.name = name;
            this.address = address;
        }
    }


    //  地址类
    @Data
    static class Address implements Serializable {
        private Integer id;
        private String city;
        public Address(Integer id, String city) {
            this.id = id;
            this.city = city;
        }
    }
}

在这里插入图片描述

通过字节流将原型对象的值写进内存,然后再从内存中读取数据进行克隆。因为是从内存中读取值所以不存在共享内存地址。
注意:原型对象以及引用对象都要实现Serializable 接口,标识自己可以被序列化,否则会抛出异常 (java.io.NotSerializableException)。

4、使用第三方工具实现深克隆,比如 Apache Commons Lang;
import org.apache.commons.lang3.SerializationUtils;
import lombok.Data;
import java.io.*;

/**
  * 深克隆实现方式四:通过 apache.commons.lang 实现
  */
public class CloneExample3 {

    public static void main(String[] args) {
        Address address = new Address(1,"广州");
        People people1 = new People(1,"Java", address);
        //  通过字节流实现克隆
        People people2 = (People) SerializationUtils.clone(people1);
        //  修改原型
        people1.getAddress().setCity("深圳");
        //  输出比对结果
        System.out.println("原型对象:" + people1.address.getCity());
        System.out.println("克隆对象:" + people2.address.getCity());
    }



    //  用户类
    @Data
    static class People implements Serializable{
        private Integer id;
        private String name;
        private Address address;
        public People(Integer id, String name, Address address) {
            this.id = id;
            this.name = name;
            this.address = address;
        }
    }


    //  地址类
    @Data
    static class Address implements Serializable {
        private Integer id;
        private String city;
        public Address(Integer id, String city) {
            this.id = id;
            this.city = city;
        }
    }
}

在这里插入图片描述

可以看出此方法和第三种实现方式类似,都需要实现 Serializable 接口,都是通过字节流的方式实现的,只不过这种实现方式是第三方提供了现成的方法,让我们可以直接调用。

5、使用 JSON 工具类实现深克隆,比如 Gson、FastJSON 等。

import lombok.Data;
import com.google.gson.Gson;
import java.io.Serializable;

public class CloneExample4 {

    public static void main(String[] args) {
        Address address = new Address(1,"广州");
        People people1 = new People(1,"Java", address);
        //  通过字节流实现克隆
        Gson gson = new Gson();
        People people2 = gson.fromJson(gson.toJson(people1), People.class);
        //  修改原型
        people1.getAddress().setCity("深圳");
        //  输出比对结果
        System.out.println("原型对象:" + people1.address.getCity());
        System.out.println("克隆对象:" + people2.address.getCity());
    }



    //  用户类
    @Data
    static class People implements Serializable {
        private Integer id;
        private String name;
        private Address address;
        public People(Integer id, String name, Address address) {
            this.id = id;
            this.name = name;
            this.address = address;
        }
    }


    //  地址类
    @Data
    static class Address implements Serializable {
        private Integer id;
        private String city;
        public Address(Integer id, String city) {
            this.id = id;
            this.city = city;
        }
    }
}

在这里插入图片描述
这种方式就是将原型对象装成JSON格式数据,再有JSON格式数据生成克隆对象,借助第三方数据存储,进行克隆。

本文是根据下面链接进行学习编写:
https://kaiwu.lagou.com/course/courseInfo.htm?courseId=59#/detail/pc?id=1767

### 深拷贝浅拷贝简介 **浅拷贝 (Shallow Copy)** 和 **深拷贝 (Deep Copy)** 是 JavaScript 中处理复杂数据结构(如对象和数组)时常见的两个概念。 #### 浅拷贝 浅拷贝是指创建一个新的对象或数组,并将原始对象的第一层属性复制到新对象中。如果这些属性是基本类型(例如数字、字符串),它们会被直接复制;但如果这些属性本身也是引用类型(例如嵌套的对象或数组),则只会复制其引用地址,而不是实际的数据内容。因此,在对深层嵌套的对象进行修改时,原对象也会受到影响。 常见实现方式包括: - 使用 `Object.assign()` 方法。 - 利用展开运算符 (`...`) 进行赋值操作。 示例代码: ```javascript const obj1 = { a: 1, b: { c: 2 } }; const shallowCopyObj = Object.assign({}, obj1); shallowCopyObj.b.c = 3; console.log(obj1); // 输出: { a: 1, b: { c: 3 } } ``` 从上面的例子可以看出,当我们改变`shallowCopyObj`中的b的c属性时,原本obj1里的相应部分也发生了变化。 #### 深拷贝 而深拷贝则是指完全独立地复制整个对象及其所有层级下的每一个元素。无论是简单类型的值还是复合类型的引用,都会被完整地克隆出来,形成新的内存空间存储副本。这意味着对新对象的操作不会影响原有的源对象。 常用的方法有: - JSON.parse(JSON.stringify()) - 第三方库如 lodash 的 cloneDeep 函数 注意点:JSON 序列化/反序列化的方案无法处理函数以及循环引用等问题; lodash 提供了一个更全面可靠的解决方案。 例子展示如何做深度复制并保持原有对象不变: ```javascript const deepCloneWithLodash = _.cloneDeep({a : 1 ,b:{c:[1,{d:'test'}]}}); // 修改克隆后的对象内部的内容不影响原来的对象 deepCloneWithLodash.b.c[1].d='changed'; originalObject={a : 1 ,b:{c:[1,{d:'test'}]}}; console.log(deepCloneWithLodash=== originalObject)// false 表明他们不是同一个引用 console.log(originalObject)//{a : 1,b:{c:[1,{d:'test'}]}} console.log(deepCloneWithLodash)//{a : 1,b:{c:[1,{d:'changed'}]}} ``` --- 总结来说,选择哪种策略取决于具体的场景需求——如果你只需要第一级的数据共享,则可以选择效率更高的浅拷贝;而对于需要彻底隔离的情况,则应考虑使用深拷贝技术。不过需要注意的是,对于某些特殊情况(比如包含函数或者存在循环依赖的情况下),传统的做法可能会出现问题,此时可以借助外部工具辅助完成任务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值