Java轻松理解深拷贝与浅拷贝

Java轻松理解深拷贝与浅拷贝

前言

对象的拷贝(克隆)是一个非常高频的操作,主要有以下三种方式:

		1.直接赋值

		2.拷贝:

				*浅拷贝

				*深拷贝

因为Java没有指针的概念,或者说是不需要我们去操心,这让我们省去了很多麻烦,但相应的,对于对象的引用、拷贝有时候就会有些懵逼,藏下一些很难发现的bug。
为了避免这些bug,理解这三种操作的作用与区别就是关键。

直接赋值
用等于号直接赋值是我们平时最常用的一种方式。

它的特点就是直接引用等号右边的对象

先来看下面的例子

先创建一个Person类
public class Person{
     
    private String name;
    private int age;
    private Person friend;
}
测试
```@Test
public void test() {
     
  Person friend =new Person("老王",30,null);
  Person person1 = new Person("张三", 20, null);
  Person person2 = person1;
  System.out.println("person1: " + person1);
  System.out.println("person2: " + person2 + "\n");
  person1.setName("张四");
  person1.setAge(25);
  person1.setFriend(friend);
  System.out.println("person1: " + person1);
  System.out.println("person2: " + person2);
}
结果
person1: Person(name=张三, age=20, friend=null)
person2: Person(name=张三, age=20, friend=null)

person1: Person(name=张四, age=25, friend=Person(name=老王, age=30, friend=null))
person2: Person(name=张四, age=25, friend=Person(name=老王, age=30, friend=null))
分析:

可以看到通过直接赋值进行拷贝,其实就只是单纯的对前对象进行引用。

如果这些对象都是基础对象当然没什么问题,但是如果对象进行操作,相当于两个对象同属一个实例。

Java轻松理解深拷贝与浅拷贝_第1张图片
拷贝

直接赋值虽然方便,但是很多时候并不是我们想要的结果,很多时候我们需要的是两个看似一样但是完全独立的两个对象。

这种时候我们就需要用到一个方法clone()

clone()并不是一个可以直接使用的方法,需要先实现Cloneable接口,然后重写它才能使用。

protected native Object clone() throws CloneNotSupportedException;

clone()方法被native关键字修饰,native关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是系统或者其他语言来实现。

浅拷贝
浅拷贝可以实现对象克隆,但是存在一些缺陷。

定义:

如果原型对象的成员变量是值类型,将复制一份给克隆对象,也就是在堆中拥有独立的空间;

如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,指向相同的内存地址。

举例
光看定义不太好一下子理解,上代码看例子。

我们先来修改一下Person类,实现Cloneable接口,重写clone()方法,其实很简单,只需要用super调用一下即可。

public class Person implements Cloneable {
     
    private String name;
    private int age;
    private Friend friend;
    @Override
    public Object clone() {
     
        try {
     
            return super.clone();
        } catch (CloneNotSupportedException e) {
     
            e.printStackTrace();
        }
        return null;
    }
}


class Friend {
     
    private String Name;
}
测试
@Test
public void test() {
     
  Person person1 = new Person("张三", 20, "老王");
  Person person2 = (Person) person1.clone();

  System.out.println("person1: " + person1);
  System.out.println("person2: " + person2 + "\n");
  person1.setName("张四");
  person1.setAge(25);
  person1.setFriend("小王");
  System.out.println("person1: " + person1);
  System.out.println("person2: " + person2);
}
结果
person1: Person(name=张三, age=20, friend=Friend(Name=老王))
person2: Person(name=张三, age=20, friend=Friend(Name=老王))

person1: Person(name=张四, age=25, friend=Friend(Name=小王))
person2: Person(name=张三, age=20, friend=Friend(Name=小王))

可以看到,name age基本对象属性并没改变,而friend引用对象熟悉变了。

原理
Java浅拷贝的原理其实是把原对象的各个属性的地址拷贝给新对象。

注意我说的是各个属性,就算是基础对象属性其实也是拷贝的地址。

你在这里会有疑问,都是拷贝了地址,为什么修改了 person1 对象的 name age 属性值,person2 对象的 name age 属性值没有改变呢?

我们来看一下,拿name属性来说明:

String、Integer 等包装类都是不可变的对象

当需要修改不可变对象的值时,需要在内存中生成一个新的对象来存放新的值,然后将原来的引用指向新的地址,我们修改了 person1 对象的 name 属性值,person1 对象的 name 字段指向了内存中新的 String 对象,但是我们并没有改变 person2 对象的 name 字段的指向,所以 person2 对象的 name 还是指向内存中原来的 String 地址

看图
Java轻松理解深拷贝与浅拷贝_第2张图片
这个图已经很清晰的展示了其中的过程,因为person1 对象改变friend时是改变的引用对象的属性,并不是新建立了一个对象进行替换,原本老王的消失了,变成了小王。所以person2也跟着改变了。
Java轻松理解深拷贝与浅拷贝_第3张图片

给孩子点个关注,点个赞吧,thanks,thanks,thanks

深拷贝
深拷贝就是我们拷贝的初衷了,无论是值类型还是引用类型都会完完全全的拷贝一份,在内存中生成一个新的对象。

拷贝对象和被拷贝对象没有任何关系,互不影响。

深拷贝相比于浅拷贝速度较慢并且花销较大。

简而言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
Java轻松理解深拷贝与浅拷贝_第4张图片
Java轻松理解深拷贝与浅拷贝_第5张图片
因为Java本身的特性,对于不可变的基本值类型,无论如何在内存中都是只有一份的。
所以对于不可变的基本值类型,深拷贝跟浅拷贝一样,不过并不影响什么。

实现:
想要实现深拷贝并不难,只需要在浅拷贝的基础上进行一点修改即可。

给friend添加一个clone()方法。

在Person类的clone()方法调用friend的clone()方法,将friend也复制一份即可。

public class Person implements Cloneable {
     
    private String name;
    private int age;
    private Friend friend;

    public Person(String name, int age, String friend) {
     
        this.name = name;
        this.age = age;
        this.friend = new Friend(friend);
    }

    public void setFriend(String friend) {
     
        this.friend.setName(friend);
    }

    @Override
    public Object clone() {
     
        try {
     
            Person person = (Person)super.clone();
            person.friend = (Friend) friend.clone();
            return person;
        } catch (CloneNotSupportedException e) {
     
            e.printStackTrace();
        }
        return null;
    }
}


public class Friend implements Cloneable{
     
    private String Name;

    @Override
    public Object clone() throws CloneNotSupportedException {
     
        return super.clone();
    }
}
测试
@Test
public void test() {
     
Person person1 = new Person("张三", 20, "老王");
Person person2 = (Person) person1.clone();

System.out.println("person1: " + person1);
System.out.println("person2: " + person2 + "\n");
person1.setName("张四");
person1.setAge(25);
person1.setFriend("小王");
System.out.println("person1: " + person1);
System.out.println("person2: " + person2);
}
结果

person1: Person(name=张三, age=20, friend=Friend(Name=老王))
person2: Person(name=张三, age=20, friend=Friend(Name=老王))

person1: Person(name=张四, age=25, friend=Friend(Name=小王))
person2: Person(name=张三, age=20, friend=Friend(Name=老王))
分析:

可以看到这次是真正的完全独立了起来。

总结
直接赋值是将新的对象指向原对象所指向的实例,所以一旦有所修改,两个对象会一起变。

浅拷贝是把原对象属性的地址传给新对象,对于不可变的基础类型,实现了二者的分离,但对于引用对象,二者还是会一起改变。

深拷贝是真正的完全拷贝,二者没有关系。实现深拷贝时如果存在多层依赖关系,可以采用序列化的方式来进行实现。

你可能感兴趣的:(后端Java,Java面试,javaSE,java,后端,eclipse,面试,编程语言)