<>概述

浅拷贝:对基本类型数据进行值传递,对引用类型数据进行引用传递的拷贝

深拷贝:对基本类型数据进行值传递,对引用类型数据创建一个新的对象,并复制其内容

<>Object#clone()

在Java中,所有的对象都继承自java.lang.Object,在Object类中提供了一个clone()方法来供我们对Java对象进行拷贝:
protected native Object clone() throws CloneNotSupportedException;
这个方法是native的,所以不需要我们来实现。

不过需要注意的是,这个方法还是**protected**的,这意味着clone()方法只在java.lang包或其子类可见。

clone()方法还抛出了一个CloneNotSupportedException异常,通过源码注释可以知道,当调用了clone()方法的对象类没有实现
Cloneable接口,或者无法克隆对象的时候,会抛出该异常,也就是说,如果要成功的调用clone()接口,首先该类要实现Cloneable接口。
/** * Thrown to indicate that the <code>clone</code> method in class *
<code>Object</code> has been called to clone an object, but that * the object's
class does not implement the <code>Cloneable</code> * interface. * <p> *
Applications that override the <code>clone</code> method can also * throw this
exception to indicate that an object could not or * should not be cloned. * *
@author unascribed * @see java.lang.Cloneable * @see java.lang.Object#clone() *
@since JDK1.0 */ public class CloneNotSupportedException extends Exception {
private static final long serialVersionUID = 5195511250079656443L; public
CloneNotSupportedException() {super();} public CloneNotSupportedException(String
s) {super(s);} }
<>Cloneable接口

Cloneable是一个空接口,对Cloneable接口可以把它理解为一个标记,是开发者允许这个类对象可以被拷贝的标记。
/** * A class implements the <code>Cloneable</code> interface to * indicate to
the {@link java.lang.Object#clone()} method that it * is legal for that method
to make a * field-for-field copy of instances of that class. * <p> * Invoking
Object's clone method on an instance that does not implement the *
<code>Cloneable</code> interface results in the exception *
<code>CloneNotSupportedException</code> being thrown. * <p> * By convention,
classes that implement this interface should override * <tt>Object.clone</tt>
(which is protected) with a public method. * See {@link
java.lang.Object#clone()} for details on overriding this * method. * <p> * Note
that this interface does <i>not</i> contain the <tt>clone</tt> method. *
Therefore, it is not possible to clone an object merely by virtue of the * fact
that it implements this interface. Even if the clone method is invoked *
reflectively, there is no guarantee that it will succeed. * * @author
unascribed * @see java.lang.CloneNotSupportedException * @see
java.lang.Object#clone() * @since JDK1.0 */ public interface Cloneable { }
通过源码注释,我们可以了解到:

* 对于实现了Cloneable接口的对象,可以调用Object#clone()来进行属性的拷贝
* 如果没有实现Cloneable接口,直接调用clone()方法则会抛出CloneNotSupportedException异常
* jdk建议我们实现Cloneable接口时,以public修饰符重写Object#clone()方法
* Cloneable是一个空接口,如果只实现了该接口,没有重写Object#clone()方法也不会调用成功。
<>浅拷贝

参考上面:对于实现了Cloneable接口的对象,可以调用Object#clone()来进行属性的拷贝。这里的拷贝就是浅拷贝

示例:
public class Test { public static void main(String[] args) throws
CloneNotSupportedException { CloneTest source = new CloneTest(10,new CloneTest()
); // 拷贝 CloneTest copy = (CloneTest)source.clone(); System.out.println("修改前:");
System.out.println(source); System.out.println(copy); // 修改属性 copy.setAge(12);
copy.getCloneTest().setAge(13); System.out.println("修改后:"); System.out.println(
source); System.out.println(copy); } } @Data @AllArgsConstructor
@NoArgsConstructor class CloneTest implements Cloneable{ int age ; CloneTest
cloneTest; @Override public Object clone() throws CloneNotSupportedException {
return super.clone(); } } ------------------------输出----------------------------
- 修改前: CloneTest(age=10, cloneTest=CloneTest(age=0, cloneTest=null)) CloneTest(
age=10, cloneTest=CloneTest(age=0, cloneTest=null)) 修改后: CloneTest(age=10,
cloneTest=CloneTest(age=13, cloneTest=null)) CloneTest(age=12, cloneTest=
CloneTest(age=13, cloneTest=null))
可以看到对copy的CloneTest属性值进行修改之后,source的CloneTest属性值也改变了

<>深拷贝

和浅拷贝不同的是,在深拷贝时,有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝。

深拷贝有两种实现方式:序列化对象方式和属性二次调用clone方法

<>二次调用clone方法
public class Test { public static void main(String[] args) throws
CloneNotSupportedException { CloneTest source = new CloneTest(10,new CloneTest()
); // 拷贝 CloneTest copy = (CloneTest)source.clone(); System.out.println("修改前:");
System.out.println(source); System.out.println(copy); // 修改属性 copy.setAge(12);
copy.getCloneTest().setAge(13); System.out.println("修改后:"); System.out.println(
source); System.out.println(copy); } } @Data @AllArgsConstructor
@NoArgsConstructor class CloneTest implements Cloneable{ int age ; CloneTest
cloneTest; @Override public Object clone() throws CloneNotSupportedException {
CloneTest clone = (CloneTest)super.clone(); if (null!=cloneTest){ //
对属性进行二次clone clone.cloneTest =(CloneTest) cloneTest.clone(); } return clone; } }
---------------输出--------------------- 修改前: CloneTest(age=10, cloneTest=
CloneTest(age=0, cloneTest=null)) CloneTest(age=10, cloneTest=CloneTest(age=0,
cloneTest=null)) 修改后: CloneTest(age=10, cloneTest=CloneTest(age=0, cloneTest=
null)) CloneTest(age=12, cloneTest=CloneTest(age=13, cloneTest=null))
通过debug可以看到,深拷贝后引用类型的属性不再指向同一个引用了

<>序列化拷贝

因为序列化拷贝是把对象转换为字节序列,再把字节序列恢复成对象,不是属性的拷贝,所以使用序列化拷贝可以不实现Cloneable接口,但要实现
Serializable接口
@Data @AllArgsConstructor @NoArgsConstructor class CloneTest implements
Serializable{ int age; CloneTest cloneTest; } public class Test { public static
void main(String[] args) throws Exception { CloneTest source = new CloneTest(10,
new CloneTest()); // 这里为了看着方便,省略了try...catch...finally... ByteArrayOutputStream
byteArrayOutputStream= new ByteArrayOutputStream(); ObjectOutputStream
objectOutputStream= new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(source); ByteArrayInputStream
byteArrayInputStream= new ByteArrayInputStream(byteArrayOutputStream.toByteArray
()); ObjectInputStream objectInputStream = new ObjectInputStream(
byteArrayInputStream); CloneTest copy = (CloneTest)objectInputStream.readObject(
); System.out.println("修改前:"); System.out.println(source); System.out.println(
copy); // 修改属性 copy.setAge(12); copy.getCloneTest().setAge(13); System.out.
println("修改后:"); System.out.println(source); System.out.println(copy); } } ----
-------------输出------------------- 修改前: CloneTest(age=10, cloneTest=CloneTest(
age=0, cloneTest=null)) CloneTest(age=10, cloneTest=CloneTest(age=0, cloneTest=
null)) 修改后: CloneTest(age=10, cloneTest=CloneTest(age=0, cloneTest=null))
CloneTest(age=12, cloneTest=CloneTest(age=13, cloneTest=null))

注意:因为transient变量无法序列化, 使用这种方法将无法拷贝transient变量。再就是性能问题。创建一个socket, 序列化一个对象,
通过socket传输它, 然后反序列化它,这个过程与调用已有对象的方法相比是很慢的。

<>如何选择拷贝方式

* 如果对象的属性全是基本类型的,那么可以使用浅拷贝。
* 如果对象有引用属性,那就要基于具体的需求来选择浅拷贝还是深拷贝。
*
意思是如果对象引用任何时候都不会被改变,那么没必要使用深拷贝,只需要使用浅拷贝就行了。如果对象引用经常改变,那么就要使用深拷贝。没有一成不变的规则,一切都取决于具体需求。

技术
下载桌面版
GitHub
Gitee
SourceForge
百度网盘(提取码:draw)
云服务器优惠
华为云优惠券
腾讯云优惠券
阿里云优惠券
Vultr优惠券
站点信息
问题反馈
邮箱:[email protected]
吐槽一下
QQ群:766591547
关注微信