面试题:深克隆和浅克隆的实现方式,7年老Java一次坑爹的面试经历

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

  • {@code x.clone().getClass() == x.getClass()}.

  • By convention, the object returned by this method should be independent

  • of this object (which is being cloned). To achieve this independence,

  • it may be necessary to modify one or more fields of the object returned

  • by {@code super.clone} before returning it. Typically, this means

  • copying any mutable objects that comprise the internal “deep structure”

  • of the object being cloned and replacing the references to these

  • objects with references to the copies. If a class contains only

  • primitive fields or references to immutable objects, then it is usually

  • the case that no fields in the object returned by {@code super.clone}

  • need to be modified.

  • The method {@code clone} for class {@code Object} performs a

  • specific cloning operation. First, if the class of this object does

  • not implement the interface {@code Cloneable}, then a

  • {@code CloneNotSupportedException} is thrown. Note that all arrays

  • are considered to implement the interface {@code Cloneable} and that

  • the return type of the {@code clone} method of an array type {@code T[]}

  • is {@code T[]} where T is any reference or primitive type.

  • Otherwise, this method creates a new instance of the class of this

  • object and initializes all its fields with exactly the contents of

  • the corresponding fields of this object, as if by assignment; the

  • contents of the fields are not themselves cloned. Thus, this method

  • performs a “shallow copy” of this object, not a “deep copy” operation.

  • The class {@code Object} does not itself implement the interface

  • {@code Cloneable}, so calling the {@code clone} method on an object

  • whose class is {@code Object} will result in throwing an

  • exception at run time.

  • @return a clone of this instance.

  • @throws CloneNotSupportedException if the object’s class does not

  •           support the {@code Cloneable} interface. Subclasses
    
  •           that override the {@code clone} method can also
    
  •           throw this exception to indicate that an instance cannot
    
  •           be cloned.
    
  • @see java.lang.Cloneable

*/

protected native Object clone() throws CloneNotSupportedException;

上述方法中的注释描述中,对于clone方法关于复制描述,提出了三个规则,也就是说,”复制“的确切定义取决于对象本身,它可以满足以下任意一条规则:

  • 对于所有对象,x.clone () !=x 应当返回 true,因为克隆对象与原对象不是同一个对象。

  • 对于所有对象,x.clone ().getClass () == x.getClass () 应当返回 true,因为克隆对象与原对象的类型是一样的。

  • 对于所有对象,x.clone ().equals (x) 应当返回 true,因为使用 equals 比较时,它们的值都是相同的。

因此,从clone方法的源码中可以得到一个结论,clone方法是深克隆还是浅克隆,取决于实现克隆方法对象的本身实现。

深克隆


理解了浅克隆,我们就不难猜测到,所谓深克隆的本质,应该是如下图所示。

图片

dylan这个对象实例从mic对象克隆之后,应该要分配一块新的内存地址,从而实现在内存地址上的隔离。

深拷贝实现的是对所有可变(没有被final修饰的引用变量)引用类型的成员变量都开辟独立的内存空间,使得拷贝对象和被拷贝对象之间彼此独立,因此一般深拷贝对于浅拷贝来说是比较耗费时间和内存开销的。

深克隆实现


修改Person类中的clone()方法,代码如下。

@Override

protected Object clone() throws CloneNotSupportedException {

Person p=(Person)super.clone(); //可以直接使用clone方法克隆,因为String类型中的属性是final修饰,而int是基本类型,都会创建副本

if(this.score!=null&&this.score.size()>0){ //如果score不为空时,才做深度克隆

//由于score是引用类型,所以需要重新分配内存空间

List ls=new ArrayList<>();

this.score.stream().forEach(score->{

Score s=new Score();

s.setFraction(score.getFraction());

s.setCategory(score.getCategory());

ls.add(s);

});

p.setScore(ls);

}

return p;

}

再次执行,运行结果如下

person对象初始化状态:Person{name=‘Mic’, age=18, score=[Score{category=‘语文’, fraction=90.0}, Score{category=‘数学’, fraction=100.0}]}

打印克隆对象:dylan:Person{name=‘Mic’, age=18, score=[Score{category=‘语文’, fraction=90.0}, Score{category=‘数学’, fraction=100.0}]}

打印mic:Person{name=‘Mic’, age=20, score=[Score{category=‘语文’, fraction=70.0}, Score{category=‘数学’, fraction=100.0}]}

打印dylan:Person{name=‘Mic’, age=18, score=[Score{category=‘语文’, fraction=90.0}, Score{category=‘数学’, fraction=100.0}]}

Process finished with exit code 0

从结果可以看到,这两个对象之间并没有相互影响,因为我们在clone方法中,对于Person这个类的成员属性Score使用new创建了一个新的对象,这样就使得两个对象分别指向不同的内存地址。

创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。总之深浅克隆都会在堆中新分配一块区域,区别在于对象属性引用的对象是否需要进行克隆(递归性的)

深克隆的其他实现方式


深克隆的实现方式很多,总的来说有以下几种:

  • 所有对象都实现克隆方法。

  • 通过构造方法实现深克隆。

  • 使用 JDK 自带的字节流。

  • 使用第三方工具实现,比如:Apache Commons Lang。

  • 使用 JSON 工具类实现,比如:Gson,FastJSON 等等。

其实,深克隆既然是在内存中创建新的对象,那么任何能够创建新实例对象的方式都能完成这个动作,因此不局限于这些方法。

所有对象都实现克隆方法

由于浅克隆本质上是因为引用对象指向同一块内存地址,如果每个对象都实现克隆方法,意味着每个对象的最基本单位是基本数据类型或者封装类型,而这些类型在克隆时会创建副本,从而避免了指向同一块内存地址的问题。

修改代码如下。

public class Person implements Cloneable {

private String name;

private int age;

private List score;

public Person() {

}

@Override

protected Object clone() throws CloneNotSupportedException {

Person p=(Person)super.clone();

if(this.score!=null&&this.score.size()>0){ //如果score不为空时,才做深度克隆

//由于score是引用类型,所以需要重新分配内存空间

List ls=new ArrayList<>();

this.score.stream().forEach(score->{

try {

ls.add((Score)score.clone()); //这里用了克隆方法

} catch (CloneNotSupportedException e) {

e.printStackTrace();

}

});

p.setScore(ls);

}

return p;

}

}

修改Score对象

public class Score implements Cloneable {

private String category;

private double fraction;

public Score() {

}

public Score(String category, double fraction) {

this.category = category;

this.fraction = fraction;

}

@Override

protected Object clone() throws CloneNotSupportedException {

return super.clone();

}

}

Person dylan=(Person)mic.clone(); //克隆一个对象

运行结果如下

person对象初始化状态:Person{name=‘Mic’, age=18, score=[Score{category=‘语文’, fraction=90.0}, Score{category=‘数学’, fraction=100.0}]}

打印克隆对象:dylan:Person{name=‘Mic’, age=18, score=[Score{category=‘语文’, fraction=90.0}, Score{category=‘数学’, fraction=100.0}]}

打印mic:Person{name=‘Mic’, age=20, score=[Score{category=‘语文’, fraction=70.0}, Score{category=‘数学’, fraction=100.0}]}

打印dylan:Person{name=‘Mic’, age=18, score=[Score{category=‘语文’, fraction=90.0}, Score{category=‘数学’, fraction=100.0}]}

通过构造方法实现深克隆。

构造方法实现深克隆,其实是我们经常使用的方法,就是使用new关键字来实例化一个新的对象,然后通过构造参数传值来实现数据拷贝。

public class Person implements Cloneable {

private String name;

private int age;

private List score;

public Person() {

}

public Person(String name, int age, List score) {

this.name = name;

this.age = age;

this.score = score;

}

}

克隆的时候,我们这么做

Person dylan=new Person(mic.getName(),mic.getAge(),mic.getScore()); //克隆一个对象

基于ObjectStream实现深克隆

在Java中,对象流也可以实现深克隆,大家可能对对象流这个名词有点陌生,它的定义如下:

  • ObjectOutputStream, 对象输出流,把一个对象转换为二进制格式数据

  • ObjectInputStream,对象输入流,把一个二进制数据转换为对象。

这两个对象,在Java中通常用来实现对象的序列化。

创建一个工具类,使用ObjectStream来实现对象的克隆,代码实现逻辑不难:

  1. 使用ObjectOutputStream,把一个对象转换为数据流存储到对象ByteArrayOutputStream中。

  2. 再从内存中读取该数据流,使用ObjectInputStream,把该数据流转换为目标对象。

public class ObjectStreamClone {

public static T clone(T t){

T cloneObj = null;

try {

// bo,存储对象输出流,写入到内存

ByteArrayOutputStream bo = new ByteArrayOutputStream();

//对象输出流,把对象转换为数据流

ObjectOutputStream oos = new ObjectOutputStream(bo);

oos.writeObject(t);

oos.close();

// 分配内存,写入原始对象,生成新对象

ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());

ObjectInputStream oi = new ObjectInputStream(bi);

// 返回生成的新对象

cloneObj = (T) oi.readObject();

oi.close();

} catch (Exception e) {

e.printStackTrace();

}

return cloneObj;

}

}

Person对象和Score对象均需要实现Serializable接口,

public class Person implements Serializable {

}

public class Score implements Serializable {}

修改测试类的克隆方法.

Person dylan=(Person)ObjectStreamClone.clone(mic); //克隆一个对象

运行结果如下:

person对象初始化状态:Person{name=‘Mic’, age=18, score=[Score{category=‘语文’, fraction=90.0}, Score{category=‘数学’, fraction=100.0}]}

打印克隆对象:dylan:Person{name=‘Mic’, age=18, score=[Score{category=‘语文’, fraction=90.0}, Score{category=‘数学’, fraction=100.0}]}

打印mic:Person{name=‘Mic’, age=20, score=[Score{category=‘语文’, fraction=70.0}, Score{category=‘数学’, fraction=100.0}]}

打印dylan:Person{name=‘Mic’, age=18, score=[Score{category=‘语文’, fraction=90.0}, Score{category=‘数学’, fraction=100.0}]}

通过对象流能够实现深克隆,其根本原因还是在于对象的序列化之后,已经脱离了JVM内存对象的范畴,毕竟一个对象序列化之后,是可以通过文件、或者网络跨JVM传输的,因此对象在反序列化时,必然需要基于该数据流重新反射生成新的对象。

问题解答

====

问题:深克隆和浅克隆的实现方式

回答:

  1. 浅克隆是指被复制对象中属于引用类型的成员变量的内存地址和被克隆对象的内存地址相同,也就是克隆对象只实现了对被克隆对象基本类型的副本克隆。

浅克隆的实现方式,可以实现Cloneable接口,并重写clone方法,即可完成浅克隆。

浅克隆的好处是,避免了引用对象的内存分配和回收,提高对象的复制效率。

  1. 深克隆是指实现对于基本类型和引用类型的完整克隆,克隆对象和被克隆对象中的引用对象的内存地址完全隔离。

最后

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  1. 浅克隆是指被复制对象中属于引用类型的成员变量的内存地址和被克隆对象的内存地址相同,也就是克隆对象只实现了对被克隆对象基本类型的副本克隆。

浅克隆的实现方式,可以实现Cloneable接口,并重写clone方法,即可完成浅克隆。

浅克隆的好处是,避免了引用对象的内存分配和回收,提高对象的复制效率。

  1. 深克隆是指实现对于基本类型和引用类型的完整克隆,克隆对象和被克隆对象中的引用对象的内存地址完全隔离。

最后

[外链图片转存中…(img-I0KEiMk7-1713393087951)]

[外链图片转存中…(img-9M8RmXyf-1713393087951)]

[外链图片转存中…(img-2CrOqaG6-1713393087952)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-mpTaWxjY-1713393087952)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 15
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值