一文深入Java浅拷贝和深拷贝

简介: 一文深入Java浅拷贝和深拷贝

在这里插入图片描述

👨🏻‍🎓博主介绍:大家好,我是芝士味的椒盐,一名在校大学生,热爱分享知识,很高兴在这里认识大家🌟
🌈擅长领域:Java、大数据、运维、电子
🙏🏻如果本文章各位小伙伴们有帮助的话,🍭关注+👍🏻点赞+🗣评论+📦收藏,相应的有空了我也会回访,互助!!!
🤝另本人水平有限,旨在创作简单易懂的文章,在文章描述时如有错,恳请各位大佬指正,在此感谢!!!

@[TOC]

值类型和引用类型

  • 理解浅拷贝之前我们需要分清楚值类型(int、float...)、包装类(Integer、Double...)以及自己定义类等类,其实就就是值类型和引用类型两种.

在这里插入图片描述

  • 就像上面这个图中,int a=1024是值类型的所以是变量a就是直接等于实际的值1024,而object obj1、object obj2显然是引用类型,obj存储的不是实际的对象,而是对象在堆中的地址。

原型模式-浅拷贝和深拷贝图解

在这里插入图片描述

  • 浅拷贝:根据上面来讲解,原型对象在被克隆后克隆出来的新对象和原型对象的地址是不一样的,在克隆出来对象中基础类型的直接复制一份的,也就是说,克隆对象修改值类型和原型对象修改各自的属性是没有半毛钱关系的,而其中的引用对象就不一样了,克隆出来的对象引用地址和原型对象的是同一个,也就说其中任意一个更改引用属性的时候都会影响到对方的属性,都是引用同一个对象。(包装类型比较特殊的引用类型,克隆之后双方都是互不干扰的)。
  • 深拷贝:原型对象在通过深拷贝之后,基础类型仍旧是各自独立的,而各自引用对象的地址指向却是不同的,也就说在克隆对象和原型对象有任意一方修改引用参数都不会影响到对方。

先上个简单的demo

/**
 * <p>
 * 学生卡类
 * </p>
 *
 * @author starrysky
 * @since 2022/2/8
 */
public  class IdCard {
    private String cName;

    public IdCard(String cName) {
        this.cName = cName;
    }

    public String getcName() {
        return cName;
    }

    public void setcName(String cName) {
        this.cName = cName;
    }

    @Override
    public String toString() {
        return "IdCard{" +
                "cName='" + cName + '\'' +
                '}';
    }
}
/**
 * <p>
 * 学生类
 * </p>
 *
 * @author starrysky
 * @since 2022/2/8
 */
public class Student implements  Cloneable {
    private int id;
    private String name;
    private IdCard idCard;

     .....省略get set方法
     
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", idCard=" + idCard +
                '}';
    }

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

    public static void main(String[] args) throws CloneNotSupportedException {
        IdCard cId = new IdCard("hhhhh");
        Student student1 = new Student(12,"student1", cId);
        Student student2 = (Student) student1.clone();

        student2.setId(1);
        student2.setName("student2");
        student2.idCard.setcName("xxxxx");
        System.out.println(student1);
        System.out.println(student2);


    }
}
  • 运行结果如下:

在这里插入图片描述

  • 包装类型和值类型的如图可知在浅拷贝后是互不相干的,但是我们自定义的IdCard引用对象一个改了就会影响另一个,这就是由于是同一个对象缘由。

那么为什么我们自定义对象是可变包装类又特殊不可变呢?

  • 其原因是遵守不可变原则,即Immutable设计模式:
  1. 类添加final修饰符,保证类不被继承。
  2. 保证所有成员变量必须私有,并且加上final修饰(不可变指的是引用不可变,也就是不可以重新指向其他对象)
  3. 不提供改变成员变量的方法,包括setter
  4. 通过构造器初始化所有成员,进行深拷贝(deep copy)
  5. 在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝
  • 比如我们随便拿一个包装类Double类分析一下
public final class Double extends Number implements Comparable<Double> {
    public static final double POSITIVE_INFINITY = 1.0 / 0.0;

    public static final double NEGATIVE_INFINITY = -1.0 / 0.0;

    public static final double NaN = 0.0d / 0.0;

    public static final double MAX_VALUE = 0x1.fffffffffffffP+1023; // 1.7976931348623157e+308

    public static final double MIN_NORMAL = 0x1.0p-1022; // 2.2250738585072014E-308

    @SuppressWarnings("unchecked")
    public static final Class<Double>   TYPE = (Class<Double>) Class.getPrimitiveClass("double");

    public static String toString(double d) {
        return FloatingDecimal.toJavaFormatString(d);
    }

    public static Double valueOf(String s) throws NumberFormatException {
        return new Double(parseDouble(s));
    }

 
    public static Double valueOf(double d) {
        return new Double(d);
    }

    public static double parseDouble(String s) throws NumberFormatException {
        return FloatingDecimal.parseDouble(s);
    }


    public static boolean isNaN(double v) {
        return (v != v);
    }

    public static boolean isInfinite(double v) {
        return (v == POSITIVE_INFINITY) || (v == NEGATIVE_INFINITY);
    }

    public static boolean isFinite(double d) {
        return Math.abs(d) <= DoubleConsts.MAX_VALUE;
    }

    private final double value;


    public Double(double value) {
        this.value = value;
    }


    public Double(String s) throws NumberFormatException {
        value = parseDouble(s);
    }


    public boolean isNaN() {
        return isNaN(value);
    }

    public boolean isInfinite() {
        return isInfinite(value);
    }
  • 首先这个Double类类是使用final关键字修饰表示不可继承,然后所有成员变量都是使用final修饰表示引用不可变,再其次就是不提供setter方法,即满足不可变原则。

如何浅拷贝

  • 如上面IdCard和Student类就是实现Cloneable接口,并且重写了clone的方法,就可以实现。

如何深拷贝

  • 有两种方法:一种继承Cloneable接口或者序列化
  • 继承Cloneable接口
/**
 * <p>
 * 学生卡类
 * </p>
 *
 * @author starrysky
 * @since 2022/2/8
 */
public class IdCard implements Cloneable{
    private  Double cName;

    public IdCard(Double cName) {
        this.cName = cName;
    }

    public Double getcName() {
        return cName;
    }

    public void setcName(Double cName) {
        this.cName = cName;
    }

    @Override
    public String toString() {
        return "IdCard{" +
                "cName='" + cName + '\'' +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
/**
 * <p>
 * 学生类
 * </p>
 *
 * @author starrysky
 * @since 2022/2/8
 */
public  class Student implements  Cloneable {
    private int id;
    private String name;
    private IdCard idCard;

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public IdCard getIdCard() {
        return idCard;
    }

    public void setIdCard(IdCard idCard) {
        this.idCard = idCard;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", idCard=" + idCard +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //需要将引用的对象也clone
        Student student = (Student)super.clone();
        student.idCard = (IdCard)idCard.clone();
        return student;
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        IdCard cId = new IdCard(12.5);
        Student student1 = new Student(12,"student1", cId);
        Student student2 = (Student) student1.clone();

        student2.setId(1);
        student2.setName("student2");
        student2.idCard.setcName(10.2);
        System.out.println(student1);
        System.out.println(student2);


    }
}
  • 执行结果:

在这里插入图片描述

  • 使用序列化和反序列化的方式深度拷贝,和Cloneable相比可以解决多层继承带来的深度克隆失效的问题
/**
 * <p>
 * 学生卡类
 * </p>
 *
 * @author starrysky
 * @since 2022/2/8
 */
public class IdCard implements Serializable {

    private static final long serialVersionUID = 1L;

    private  Double cName;

    public IdCard(Double cName) {
        this.cName = cName;
    }

    public Double getcName() {
        return cName;
    }

    public void setcName(Double cName) {
        this.cName = cName;
    }

    @Override
    public String toString() {
        return "IdCard{" +
                "cName='" + cName + '\'' +
                '}';
    }

}
/**
 * <p>
 * 学生类
 * </p>
 *
 * @author starrysky
 * @since 2022/2/8
 */
public  class Student implements Serializable {
    private static final long serialVersionUID = 1L;
    private int id;
    private String name;
    private IdCard idCard;

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public IdCard getIdCard() {
        return idCard;
    }

    public void setIdCard(IdCard idCard) {
        this.idCard = idCard;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", idCard=" + idCard +
                '}';
    }


    public Student deepClone() {
        Student student = null;
        try {
            // 序列化
             ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
             ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
             objectOutputStream.writeObject(this);
             //反序列化
             ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
             ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
             student = (Student) objectInputStream.readObject();
        }catch (ClassNotFoundException | IOException e){
            e.printStackTrace();
        }
        return student;

    }

    public static void main(String[] args) throws CloneNotSupportedException {
        IdCard cId = new IdCard(12.5);
        Student student1 = new Student(12,"student1", cId);
        Student student2 = (Student) student1.deepClone();

        student2.setId(1);
        student2.setName("student2");
        student2.idCard.setcName(10.2);
        System.out.println(student1);
        System.out.println(student2);


    }
}
  • 执行结果:

在这里插入图片描述

集合浅拷贝和深拷贝

LinkedList浅拷贝
  • 通过构造方式
        IdCard ggc = new IdCard(1, "ggc");
        List<IdCard> list1 = new LinkedList<>();
        list1.add(ggc);
        List<IdCard> list2 = new LinkedList<>(list1);
        list2.get(0).setName("mm");
        System.out.println(list1);
        System.out.println(list2);
  • 执行结果:

在这里插入图片描述

  • 通过forEach一个个插入
        IdCard ggc = new IdCard(1, "ggc");
        List<IdCard> list1 = new LinkedList<>();
        list1.add(ggc);
        List<IdCard> list2 = new LinkedList<>();
        list1.forEach(s->list2.add(s));
        list2.get(0).setName("mm");
        System.out.println(list1);
        System.out.println(list2);
  • 执行结果:

在这里插入图片描述

  • 数组通过System.arraycopy
        IdCard ggc = new IdCard(1, "ggc");
        IdCard[] arr1 = new IdCard[]{ggc};
        IdCard[] arr2 = new IdCard[1];
        System.arraycopy(arr1,0,arr2,0,arr1.length);
        arr2[0].setName("hhhh");
        Arrays.asList(arr1).forEach(System.out::println);
        Arrays.asList(arr2).forEach(System.out::println);
  • 执行结果

在这里插入图片描述

LinkedList深拷贝
  • 通过list自带的addAll()方法
        List<Integer> list1 = new LinkedList<>(Arrays.asList(3,5,2,1));
        List<Integer> list2 = new LinkedList<>();
        list2.addAll(list1);
        list2.remove(0);
        System.out.println(list1);
        System.out.println(list2);
  • 执行结果:

在这里插入图片描述

  • 通过序列化和反向序列化

    /**
     * <p>
     *
     * </p>学生卡
     *
     * @author starrysky
     * @since 2022/2/8
     */
    public class IdCard implements Serializable {
        private static final long serialVersionUID = 1L;
        private int Id;
        private String name;
    
    
        public IdCard(int id, String name) {
            Id = id;
            this.name = name;
        }
    
        public int getId() {
            return Id;
        }
    
        public void setId(int id) {
            Id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "IdCard{" +
                    "Id=" + Id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    
        public static <T> List<T> deepClone(List<T> source) throws IOException, ClassNotFoundException {
        //序列化
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(source);
        //反向序列化
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream);
        List<T> o = (List<T>)inputStream.readObject();
        return o;
    
    }
        public static void main(String[] args) throws IOException, ClassNotFoundException {
    
        List<IdCard> list1 = new LinkedList<>();
        list1.add(new IdCard(1,"123"));
        list1.add(new IdCard(2,"1233"));
        List<IdCard> list2 = deepClone(list1);
        list2.remove(0);
        System.out.println(list1);
        System.out.println(list2);
    }
  • 执行结果:

在这里插入图片描述

  • 实现Cloneable接口,重写clone方法
    @Override
    public Object clone(){
        IdCard clone =null;
        try {
            clone = (IdCard) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clone;
    }
        IdCard idCard = new IdCard(1, "213");
        IdCard idCard2 = (IdCard) idCard.clone();
        idCard2.setName("gccc");
        System.out.println(idCard);
        System.out.println(idCard2);
  • 执行结果:

在这里插入图片描述

相关文章
|
6月前
|
Java
Java的浅拷贝与深拷贝
Java的浅拷贝与深拷贝
53 0
|
6月前
|
Java Apache
Java中的深拷贝与浅拷贝
Java中的深拷贝与浅拷贝
45 0
|
5月前
|
Java
“深入探讨Java中的对象拷贝:浅拷贝与深拷贝的差异与应用“
“深入探讨Java中的对象拷贝:浅拷贝与深拷贝的差异与应用“
|
5月前
|
存储 Dubbo Java
一篇文章讲明白Java的深拷贝和浅拷贝
一篇文章讲明白Java的深拷贝和浅拷贝
52 0
|
6月前
|
Java
【JAVA面试题】什么是深拷贝?什么是浅拷贝?
【JAVA面试题】什么是深拷贝?什么是浅拷贝?
|
Java
Java深拷贝和浅拷贝Map对象
Java深拷贝和浅拷贝Map对象
197 0
|
Java
【java面试题】- java深拷贝和浅拷贝区别?什么是引用拷贝?
java深拷贝和浅拷贝区别?什么是引用拷贝?
88 0
|
存储 Java
java深入理解浅拷贝和深拷贝
java深入理解浅拷贝和深拷贝
|
7天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
下一篇
无影云桌面