当前位置:   article > 正文

基于java实现浅拷贝和深拷贝_java浅拷贝和深拷贝的实现

java浅拷贝和深拷贝的实现

1、概念

浅拷贝:在拷贝一个对象时,复制基本数据类型的成员变量,但对引用数据类型的成员变量只进行引用的传递(复制其地址引用),并不会创建一个新的对象。简单地说就是被拷贝对象和浅拷贝得到的新对象,它们的引用数据类型的成员变量指向同一个内存地址。

深拷贝:在拷贝一个对象时,除了复制基本数据类型的成员变量,对引用数据类型的成员变量进行拷贝时,会创建一个新的对象来保存引用类型的成员变量。
简单地说就是被拷贝对象和深拷贝得到的新对象,它们的引用数据类型的成员变量指向不同的内存地址。

定义如下类:

class Person {
    private int age;
    
    private StringBuffer name;
}
  • 1
  • 2
  • 3
  • 4
  • 5

对 Person 类的对象拷贝时,浅拷贝和深拷贝示意图区别如下:
在这里插入图片描述


2、浅拷贝

Java已经内置了 Cloneable 抽象原型接口,自定义的类型只需实现该接口并重写 Object.clone() 方法即可完成本类的浅拷贝。

Cloneable 是一个空接口。Java之所以提供 Cloneable 接口,只是为了在运行时通知Java虚拟机可以安全地在该类上使用 clone() 方法。而如果该类没有实现 Cloneable 接口,则调用 clone() 方法会抛出 CloneNotSupportedException 异常。

一般情况下,如果使用 clone() 方法,则需满足以下条件:
    1、对任何对象 o,都有 o.clone() != o。换言之,克隆对象与原型对象不是同一个对象;
    2、对任何对象 o,都有 o.clone().getClass() == o.getClass()。换言之,复制对象与原对象的类型一样;
    3、如果对象 o 的 equals() 方法定义恰当,则 o.clone().equals(o) 应当成立。

2.1、浅拷贝实战

假设,现在有两个类,学生 Student 和班级 Class,每个学生都有自己对应的班级:

/**
 * @Des 班级实体类
 * @Author jidi
 * @Email jidi_jidi@163.com
 * @Date 2022/7/11
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Class {

    private int id;

    private String name;

    private String desc;
}


/**
 * @Des 学生实体类
 * @Author jidi
 * @Email jidi_jidi@163.com
 * @Date 2022/7/11
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Cloneable{
    private int id;

    private String name;

    private int age;

    /**
     * 班级,一对一关系
     */
    private Class classs;

    /**
     * 重写 Object.clone()方法
     */
    @Override
    public Student clone() throws CloneNotSupportedException {
        /**
         * super.clone()方法直接从堆内存中以二进制流的方式进行复制,重新分配一个内存块,其效率很高;
         * 由于super.clone()方法基于内存复制,不会调用对象的构造函数,也就是不需要经历初始化过程;
         * 使用super.clone()方法,如果类中存在引用对象属性,则原型对象与克隆对象的该属性会指向同一对象的引用。
         */
        return (Student)super.clone();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

浅拷贝测试:

/**
 * @Des 浅拷贝测试
 * @Author jidi
 * @Email jidi_jidi@163.com
 * @Date 2022/7/11
 */
public class StudentTest {

    public static void main(String[] args) throws CloneNotSupportedException {
        Class classs = new Class(1, "一年级", "一年级的学生比较调皮");
        Student student = new Student(1, "基地", 7, classs);

        // 浅拷贝对象
        Student clone = student.clone();
        
        // 结果为 true,指向的是同一个内存地址
        System.out.println(student.getClasss() == clone.getClasss());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

3、深拷贝

深拷贝可以有以下几种不同的实现方式:
    1、嵌套 clone 方法:在需要克隆的对象以及该对象的引用类型的变量的类中全部实现 cloneable 接口,但是对于层级比较深的对象,不太友好;
    2、使用序列化流:使要序列化的对象和该对象的引用类型成员变量对象的类都实现 Serializable 接口,将对象序列化到输出流中,然后再反序列化为对象就完成了完全的复制操作。但是静态的成员和 transient 关键字修饰的成员不能被序列化;
    3、使用开源工具类,例如,json类库(FastJson,GSON等将对象转化为json字符串,然后将json字符串转换为对象),Spring的BeanUtils,Cglib的BeanCopier。

3.1、嵌套 clone 方法

定义实体类 Student 和 Class,两个实体类都实现 Cloneable 接口并重写 Object.clone(),对于引用数据类型的变量继续使用其重写的 clone 方法进行拷贝:

/**
 * @Des 班级实体类
 * @Author jidi
 * @Email jidi_jidi@163.com
 * @Date 2022/7/11
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Class implements Cloneable{

    private int id;

    private String name;

    private String desc;

    /**
     * 重写 Object.clone()方法
     */
    @Override
    public Class clone() throws CloneNotSupportedException {
        return (Class)super.clone();
    }
}


/**
 * @Des 学生实体类
 * @Author jidi
 * @Email jidi_jidi@163.com
 * @Date 2022/7/11
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Cloneable{
    private int id;

    private String name;

    private int age;

    /**
     * 班级,一对一关系
     */
    private Class classs;

    /**
     * 重写 Object.clone()方法
     */
    @Override
    public Student clone() throws CloneNotSupportedException {
        Student student = (Student)super.clone();
        /**
         * 对于引用数据类型,继续调用clone方法
         * 如果引用数据类型嵌套层级很多,每个层级都需要处理
         */
        student.classs = this.classs.clone();
        return student;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

测试:

/**
 * @Des 深拷贝测试(嵌套 clone)
 * @Author jidi
 * @Email jidi_jidi@163.com
 * @Date 2022/7/11
 */
public class StudentTest {

    public static void main(String[] args) throws CloneNotSupportedException {
        Class classs = new Class(1, "一年级","一年级的学生比较调皮");
        Student student = new Student(1, "基地",7, classs);

        // 深拷贝对象
        Student clone = student.clone();

        // 结果为false
        System.out.println(student.getClasss() == clone.getClasss());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

3.2、使用序列化流

定义序列化工具类 SerialCloneUtils :

/**
 * @Des
 * @Author jidi
 * @Email jidi_jidi@163.com
 * @Date 2022/7/12
 */
@Slf4j
public class SerialCloneUtils {

    /**
     * @Desc 使用ObjectStream序列化实现深克隆
     * @Author jidi
     * @date 2022/7/12 9:52
     */
    public static <T extends Serializable> T deepClone(T t) throws CloneNotSupportedException {
        ByteArrayOutputStream bout = null;
        ObjectOutputStream out = null;
        InputStream bin = null;
        ObjectInputStream in = null;
        try {
            // 保存对象为字节数组
            bout = new ByteArrayOutputStream();
            out = new ObjectOutputStream(bout);
            out.writeObject(t);

            // 从字节数组中读取克隆对象
            bin = new ByteArrayInputStream(bout.toByteArray());
            in = new ObjectInputStream(bin);
            return (T)(in.readObject());
        }catch (IOException | ClassNotFoundException e){
            CloneNotSupportedException cloneNotSupportedException = new CloneNotSupportedException();
            e.initCause(cloneNotSupportedException);
            throw cloneNotSupportedException;
        }finally {
            try {
                if (Objects.nonNull(bout)){
                    bout.close();
                }
                if (Objects.nonNull(out)){
                    out.close();
                }
                if (Objects.nonNull(bin)){
                    bin.close();
                }
                if (Objects.nonNull(in)){
                    in.close();
                }
            } catch (IOException e) {
                log.error("关闭IO流失败:{}", e);
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

创建一个公共父类 SerialClone ,只要继承该类就可以实现深克隆:

/**
 * @Des
 * @Author jidi
 * @Email jidi_jidi@163.com
 * @Date 2022/7/12
 */
public class SerialClone implements Cloneable, Serializable {
    private static final long serialVersionUID = -3556726131986995463L;

    @Override
    public Object clone() throws CloneNotSupportedException {
        return SerialCloneUtils.deepClone(this);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

定义实体类 Student 和 Class,分别继承 SerialClone 类:

/**
 * @Des 班级实体类
 * @Author jidi
 * @Email jidi_jidi@163.com
 * @Date 2022/7/11
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Class extends SerialClone{

    private int id;

    private String name;

    private String desc;
}


/**
 * @Des 学生实体类
 * @Author jidi
 * @Email jidi_jidi@163.com
 * @Date 2022/7/11
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student extends SerialClone{
    private int id;

    private String name;

    private int age;

    /**
     * 班级,一对一关系
     */
    private Class classs;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

测试:

/**
 * @Des 深复制测试
 * @Author jidi
 * @Email jidi_jidi@163.com
 * @Date 2022/7/11
 */
public class StudentTest {

    public static void main(String[] args) throws CloneNotSupportedException {
        Class classs = new Class(1, "一年级","一年级的学生比较调皮");
        Student student = new Student(1, "基地",7, classs);

        // 深拷贝对象
        Student clone = (Student)student.clone();

        // 结果为false
        System.out.println(student.getClasss() == clone.getClasss());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

3.3、使用开源工具类

此处使用 FastJson 实现深拷贝:

/**
 * @Des 班级实体类
 * @Author jidi
 * @Email jidi_jidi@163.com
 * @Date 2022/7/11
 */
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Class{

    private int id;

    private String name;

    private String desc;
}

/**
 * @Des 学生实体类
 * @Author jidi
 * @Email jidi_jidi@163.com
 * @Date 2022/7/11
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student{
    private int id;

    private String name;

    private int age;

    /**
     * 班级,一对一关系
     */
    private Class classs;
}


/**
 * @Des 深复制测试
 * @Author jidi
 * @Email jidi_jidi@163.com
 * @Date 2022/7/11
 */
public class StudentTest {

    public static void main(String[] args) throws CloneNotSupportedException {
        Class classs = new Class(1, "一年级","一年级的学生比较调皮");
        Student student = new Student(1, "基地",7, classs);

        // 使用 FastJson 深拷贝对象
        Student clone = JSONObject.parseObject(JSONObject.toJSONBytes(student), Student.class);

        // 结果为false
        System.out.println(student.getClasss() == clone.getClasss());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

可以看到使用JSON类库进行深拷贝时,实体类不需要实现任何接口即可。但是需要注意的是,不同的JSON类库序列化规则不一样,对于某些表示是否的变量,命名时最好不要使用 isXxx 形式,否则会导致序列化失败。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/68604
推荐阅读
相关标签
  

闽ICP备14008679号