likes
comments
collection
share

Java和Kotlin中的深拷贝与浅拷贝

作者站长头像
站长
· 阅读数 12

Java与Kotlin中的深拷贝与浅拷贝

1、传引用与拷贝的区别

请看如下代码

Student s1 = new Student();
Student s2;
s2 = s1;

以上的代码就是一个传递引用的例子,s1与s2是同一处引用,它们指向的是内存地址中的同一处对象,而拷贝应该是新创建一个对象。

2、深拷贝与浅拷贝的区别

这两者的本质就是引用数据类型的拷贝问题,也就是拷贝层次。

基本数据类型没有区别,但是两个对象中如果有属性是引用数据类型,浅拷贝不会重新创建一个新的对象,这两个对象的这个属性它们指向的是同一处引用,修改其中一个对象的这个属性会影响到另一个对象;而深拷贝不会,深拷贝会重新创建一个对象,将这个对象赋值给拷贝目标对象的那个属性身上。

3、Java中的浅拷贝以及深拷贝的实现

在Java中,所有类的基类Object类有一个clone()方法,它默认可以实现一个浅拷贝,如下示例

public class Copy {
    public static void main(String[] args) throws CloneNotSupportedException {
        Student s1 = new Student("AB"18);
        Student s2 = (Student) s1.clone();
        s2.setAge(20);

        System.out.println(s1);
        System.out.println(s2);
    }
}

class Student implements Cloneable {
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    private String name;
    private Integer age;

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

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public Student() {
    }
}

输出如下

Student{name='AB', age=18}
Student{name='AB', age=20}

我们发现改变s2对象的age属性并没有影响到s1对象,所以这s2与s1确实不是同一个对象,我们实现了一个对象的拷贝。但为什么说这个默认实现的clone()方法是一个浅拷贝呢?我们做如下改造

public class Copy {
    public static void main(String[] args) throws CloneNotSupportedException {
        Student s1 = new Student("AB"18"math");
        Student s2 = (Student) s1.clone();
        s2.setAge(20);
        s2.getSubject().setName("english");

        System.out.println(s1);
        System.out.println(s2);
    }
}

class Student implements Cloneable {
    
    private String name;
    private Integer age;
    private Subject subject;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + ''' +
                ", age=" + age +
                ", subject=" + subject +
                '}';
    }

    public Subject getSubject() {
        return subject;
    }

    public void setSubject(Subject subject) {
        this.subject = subject;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Student(String name, Integer age, String subject) {
        this.name = name;
        this.age = age;
        this.subject = new Subject(subject);
    }

    public Student() {
    }
}

class Subject {
    private String name;

    @Override
    public String toString() {
        return "Subject{" +
                "name='" + name + ''' +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public Subject(String name) {
        this.name = name;
    }
}

输出如下

Student{name='AB', age=18, subject=Subject{name='english'}}
Student{name='AB', age=20, subject=Subject{name='english'}}

我们会发现我们向Student类中添加一个引用数据类型Subject类型的属性,我们还是使用默认的clone()方法实现,当我们改变s2的subject属性的name属性时会影响s1对象,也就是说s1对象中的subject属性和s1对象中的subject属性是同一个引用,这也就是浅拷贝。

当然要解释一下,Student类中的有参构造中第三个参数传入String类型而不传入Subject类型是故意为之,很多时候有这种需求场景,如果直接传入Subject类型的参数还是可以成功拷贝的。

那Java中我们怎么去做一下深拷贝呢,比较简单的方式就是我们自己去重写一下这个clone方法就好了,如下

@Override
  protected Object clone() throws CloneNotSupportedException {
      Student clone = (Student) super.clone();
      clone.setSubject(new Subject(this.subject.getName()));
      return clone;
  }

改完后输出如下

Student{name='AB', age=18, subject=Subject{name='math'}}
Student{name='AB', age=20, subject=Subject{name='english'}}

我们发现简单修改后就实现了深拷贝,当然这个深拷贝也是相对而言的,比如Subject类中也有了一个引用数据类型的属性,那就要拷贝两层了,其实实现的方式很多,用这种简单的方式也可以,不在过多介绍。

4、Kotlin中的浅拷贝与深拷贝的实现

Kotlin中有数据类(data class),数据类中自带一个copy()函数,这个函数跟Java中的clone()方法是一样的,也是默认实现一个浅拷贝。 还是Java中的例子,简单演示一下。

fun main() {
    val s1 = Student("Jack"18)
    val s2 = s1.copy()
    s2.age = 20
    
    println(s1)
    println(s2)
}

data class Student(val name: String, var age: Int)

输出如下

Student(name=Jack, age=18)
Student(name=Jack, age=20)

那我们发现copy()函数确实跟Java中的clone()方法很像,而且它也是实现了一个浅拷贝,这里就不作验证了。

多提一句,我们其实可以看到Kotlin中的代码量明显变少,一方面Kotlin中的这个数据类默认实现了toString()、equals()、hashCoe()等方法,为了简化一些没有业务逻辑只负责封装数据的类的写法,减少重复代码量;另一方面,Kotlin的语法糖真的出了名的多,不仅是数据类,它很多的写法都很简洁,在Java的一些基础上做了很多改进。

接下来我们看一下在Kotlin中怎么简单写一下深拷贝,跟Java一样,在数据类中重写一下copy()函数就可以了(其实这里严格来说也不是重写,只是我们写一个copy函数它就会调用我们自己写的copy函数)。如下

fun main() {
    val s1 = Student("Jack"18"math")
    val s2 = s1.copy()
    s2.age = 20
    s2.subject.name = "english"

    println(s1)
    println(s2)
}

data class Student(val name: String, var age: Intval subject: Subject) {
    constructor(name: String, age: Int, subject: String) : this(name, age, Subject(subject))

    fun copy() = Student(this.name, this.age, Subject(this.subject.name))
}

data class Subject(var name: String)

输出如下

Student(name=Jack, age=18, subject=Subject(name=math))
Student(name=Jack, age=20, subject=Subject(name=english))

这样我们就用Kotlin简单实现了一下深拷贝。

5、简单总结

  • 浅拷贝与深拷贝的本质问题就是拷贝的层次问题,浅拷贝相对不会将对象的引用数据类型的属性拷贝一份
  • Java中使用clone()方法默认实现浅拷贝,可以通过重写简单实现一个深拷贝
  • Kotlin中数据类默认的copy()函数默认实现浅拷贝,可以通过重写简单实现一个深拷贝

关于深拷贝与浅拷贝的内容就简单聊到这,还有很多实现深拷贝的方式,更加地合理和高级,感兴趣可以去搜集学习。

文章若由错误之处,欢迎指正!

转载自:https://juejin.cn/post/7225584540771762234
评论
请登录