• 欢迎访问 winrains 的个人网站!
  • 本网站主要从互联网整理和收集了与Java、网络安全、Linux等技术相关的文章,供学习和研究使用。如有侵权,请留言告知,谢谢!

Java深浅拷贝

Java技术 winrains 来源:雨点的名字 1年前 (2019-08-30) 49次浏览

一、前言

我觉得主要跟JVM内存分配有关,对于基本数据类型,只存在栈内存,所以它的拷贝不存在深浅拷贝这个概念。而对于对象而言,一个对象的创建会在内存中分配两块空间,一个在栈内存存对象的引用指针,一个在堆内存存放对象。这个时候会有一个问题,你拷贝的只是这个引用指针还是拷贝两块内存一起拷贝,这个时候就会有深浅拷贝一说。
还有之前我认为Arrays.copyOf()是深度拷贝,亲测后发现原来它也是浅拷贝。下面进行具体说明。

二、数据类型

数据分为基本数据类型(int, boolean, double, byte, char等)和对象数据类型。
基本数据类型的特点:直接存储在栈(stack)中的数据.
引用数据类型的特点:在栈内存存储对象引用,真实的数据存放在堆内存里
引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

三、什么是浅拷贝和深拷贝

首先需要明白,深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。那先来看看浅拷贝和深拷贝的概念。
在 Java 中,除了基本数据类型(元类型)之外,还存在 类的实例对象 这个引用数据类型。而一般使用 =号做赋值操作的时候。对于基本数据类型,实际上是拷贝的它的值,但是对于对象而言,其实赋值的只是这个对象的引用,将原对象的引用传递过去,他们实际上还是指向的同一个对象。
浅拷贝:如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象。
深拷贝:在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量。
深拷贝和浅拷贝的示意图大致如下:


具体接下来代码演示。

四、代码演示

1、浅拷贝

Person

public class Person {
    public String name;
    public Integer age;
    public String sex;
    /**
     * 提供get和set方法和全参构造函数
     */
}

Test

public static void main(String[] args) throws Exception {
    Person person = new Person("小小", 3, "女");
    // 将person值赋值给person1
    Person person1 = person;
    System.out.println(person);
    System.out.println(person1);
    person1.setName("小小她爸");
    System.out.println("person 中 name为:" + person.getName());
    System.out.println("person1 中 name为:" + person.getName());
}

查看运行结果

从图片中我们可以很明显看出,它们指向的内存地址是一致的,同样我改变person1的属性值时发现person的属性值也改变了。
说明:对于对象用"=" 赋值 其实只是引用指针的复制,这两个引用还是指向同一个对象。

2、深拷贝

如果要实现深拷贝就会比较复杂点
Student

/**
 * 如果对象要实现深拷贝 那么实体需要做两步
 * 1、实体实现Cloneable接口
 * 2、重写 clone()方法
 */
public class Student implements Cloneable {
    public String name;
    public Integer age;
    public String sex;
    //这也是个实体
    public Address address;
    /**
     * 提供get和set方法和全参构造函数
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Test

public static void main(String[] args) throws Exception {
    Student student = new Student("小小", 3, "女", null);
    // 将person值赋值给person1
    Student student1 = (Student) student.clone();
    System.out.println(student);
    System.out.println(student1);
    student1.setName("小小她爸");
    System.out.println("person 中 name为:" + student.getName());
    System.out.println("person1 中 name为:" + student1.getName());
}


这里可以已经是两个不同的对象了。但是这里需要注意的是,如果对象中含有对象,这个对象还是浅拷贝。
Address

public class Address {
    public String city;
    public int phone;
    /**
     * 提供get和set方法和全参构造函数
     */
}

Test

public static void main(String[] args) throws Exception {
    Address address = new Address("杭州", 1888888888);
    Student student2 = new Student("小小", 3, "女", address);
    // 将person值赋值给person1
    Student student3 = (Student) student2.clone();
    address.setCity("北京天安门");
    System.out.println("person2 中 city为:" + student2.getAddress().getCity());
    System.out.println("person3 中 city为:" + student3.getAddress().getCity());
}


我们发现虽然Student是实现了深拷贝,但Address却还是浅拷贝,那如何让Adress也实现深拷贝呢。
Address修改

public class Address implements Cloneable {
    public String  city;
    public  int phone;
   /**
     * 提供get和set方法和全参构造函数
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Student修改

// 修改clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
    Student s = (Student) super.clone();
    s.address = (Address) address.clone();
    return s;
}


弊端: 这里我们Person 类只有一个 Address 引用类型,而 Address 类没有,所以我们只用重写 Address 类的clone 方法,但是如果 Address 类也存在一个引用类型,
那么我们也要重写其clone 方法,这样下去,有多少个引用类型,我们就要重写多少次,如果存在很多引用类型,那么代码量显然会很大,所以这种方法不太合适。
所以还有另一种实现深拷贝方法。
序列化实现深拷贝

//序列化实现深拷贝
public Object deepClone() throws Exception{
    // 序列化
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(this);
    // 反序列化
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    return ois.readObject();
}
 //因为序列化产生的是两个完全独立的对象,所有无论嵌套多少个引用类型,序列化都是能实现深拷贝的。

五、Arrays.copyOf()

之前我误以为Arrays.copyOf()为深拷贝,那只是因为我用的是基本数据类型作为数组,而基本数据类型上面已经说过它没有深浅拷贝这个概念,可以把他理解成只有深拷贝。

public static void main(String[] args) {
    // 1、基本数据类型
    int[] a = { 0, 1, 2, 3 };
    // Arrays.copyOf拷贝
    int[] copy = Arrays.copyOf(a, a.length);
    a[0] = 1;
    System.out.println(Arrays.toString(copy));
    System.out.println(Arrays.toString(a));
    // 2、对象数组
    Student[] stuArr = { new Student("小小", 3, "女"), new Student("小小爸", 29, "男"), new Student("小小妈", 27, "女") };
    // Arrays.copyOf拷贝
    Student[] copyStuArr = Arrays.copyOf(stuArr, stuArr.length);
    copyStuArr[0].setName("小小爷爷");
    System.out.println(Arrays.toString(stuArr));
    System.out.println(Arrays.toString(copyStuArr));
}

运行结果:

可以明显看出,对于基本数据类型只有深拷贝,而对于数组对象而言,明显存在深浅拷贝,而且可以看出Arrays.copyOf()为浅拷贝

作者:雨点的名字

来源:https://www.cnblogs.com/qdhxhz/p/10527245.html


版权声明:文末如注明作者和来源,则表示本文系转载,版权为原作者所有 | 本文如有侵权,请及时联系,承诺在收到消息后第一时间删除 | 如转载本文,请注明原文链接。
喜欢 (1)