Java和Kotlin中的深拷贝与浅拷贝
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: Int, val 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