Java的浅拷贝与深拷贝详细解析
@[toc]
一、认识浅拷贝与深拷贝
对于=赋值,相对于基本数据类型实际上就是直接拷贝它的值,而对于引用数据类型则只是传递这个对象的引用,将原对象的引用实际上还是指向的同一个对象。
浅拷贝
:拷贝一个对象时,只对基本数据类型进行拷贝,而对于引用数据类型只是进行了引用的传递,并没有正式的创建一个新的对象。
深拷贝
:相对于浅拷贝不同的是,针对于引用数据类型的拷贝是创建了一个新的对象,并且复制其中的成员变量。
引用赋值:
class Person{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class AnnotationTest {
@Test
public void test(){
Person person = new Person("长路", 18);
Person person2 = person;
System.out.println(person);//xyz.changlu.Person@621be5d1
System.out.println(person2);//xyz.changlu.Person@621be5d1
}
}
- 这种方式既不属于浅拷贝也不属于深拷贝就是简单的引用传递。
person
与person2
实例都指向堆中同一个引用地址。
接下来我们来通过例子探讨浅拷贝与深拷贝实现方式。
二、认识clone()方法
首先看一下Object类
中的clone()
方法:
public class Object {
//native修饰符说明该方法是一个本地方法
protected native Object clone() throws CloneNotSupportedException;
}
- 实现了
Cloneable
接口即可使用Object
的clone()
方法。
如何使用这个clone
方法呢?需要搭配一个Cloneable
接口。
public interface Cloneable {
}
- 是的该接口没有方法是不是很奇怪。
讲述这两者之间联系:自定义类实现该接口表名当前对象可以clone,实现了该接口后能够改变父类Object类中的clone方法,且能够调用Object
的clone
方法;若直接调用clone()
方法否则会抛出CloneNotSupportedException
异常。
三、实现浅拷贝
3.1、clone()方法
对下面的Person
进行浅拷贝,利用clone()
方法来拷贝:
class Wallet{
private Integer money = 100;
}
class Person implements Cloneable{
private String name;
private int age;
private Wallet wallet;
public Person(String name, int age,Wallet wallet) {
this.name = name;
this.age = age;
this.wallet = wallet;
}
public Wallet getWallet() {
return wallet;
}
@Override
public Object clone(){
Person person = null;
try {
person = (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return person;
}
}
public class AnnotationTest {
@Test
public void test() throws CloneNotSupportedException {
Person person = new Person("长路", 18,new Wallet());
Person clonePerson = (Person) person.clone();
//使用clone()获得的对象与原先的引用地址不一样,是在堆中新开辟的
System.out.println(person == clonePerson);//false
//查看下其中的引用类型是否直接拷贝了引用,true表示对引用数据类型直接赋引用地址
System.out.println(person.getWallet() == clonePerson.getWallet());//true
}
}
- 首先是
Person
类实现Cloneable
接口。 - 接着重写了
Object
的clone()
方法,有几个改变点(注意点):- 将
protected
修饰符更改为public
,为啥呢,因为使用protected
修饰符在不同包中类无法使用,除非该类是Person
的子类,方便其他人调用。 - 不抛出异常,而是直接在方法中
catch
异常。 - 关键浅拷贝部分,
super.clone()
实际上就是调用父类的clone方法(即Object类的),对本实例进行浅拷贝。
- 将
总结:使用上面方式的浅拷贝的实例会复制一份新的实例出来,但其中的成员属性内容对于引用类型都是直接拷贝引用而不是重新创建新的对象。(若是想要引用类型属性也重新创建新对象,那么其引用属性也要重写clone()方法,并且在clone好Person之后对其引用属性再重新调用clone()方法)
3.2、System.arraycopy()
该方法属于System
类中静态本地方法,是浅拷贝:
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
在jdk中调用该本地方法有:Arrays.copyOf([] original, int newLength, Class<? extends T[]> newType)
、ArrayList.clone()
测试程序:通过使用ArrayList
来进行测试
class Person{
private String name = "changlu";
private int age = 18;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) {
ArrayList<Person> list = new ArrayList<>();
list.add(new Person());
//克隆的ArrayList集合
ArrayList<Person> clone = (ArrayList<Person>) list.clone();
clone.get(0).setName("liner");//对该集合中的元素name进行修改看是否改变了原来的对象实例
//打印原来集合
System.out.println(list);
}
}
说明:可以看到对克隆集合中的元素进行修改,同时也改变了原有的集合元素,说明该方法是浅拷贝。
四、实现深拷贝
方式一:使用clone()方法
import org.junit.Test;
class Wallet implements Cloneable{
private Integer money = 100;
//1、实现cloneable接口,并重写clone()方法,其中调用Object的clone()方法
@Override
public Object clone() {
Wallet wallet = null;
try {
wallet = (Wallet) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return wallet;
}
@Override
public String toString() {
return "Wallet{" +
"money=" + money +
'}';
}
}
class Person implements Cloneable{
private String name;
private int age;
private Wallet wallet;
public Person(String name, int age,Wallet wallet) {
this.name = name;
this.age = age;
this.wallet = wallet;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setWallet(Wallet wallet) {
this.wallet = wallet;
}
public Wallet getWallet() {
return wallet;
}
@Override
public Object clone(){
Person person = null;
try {
person = (Person) super.clone();
//2、对person实例中的wallet进行重新拷贝
person.setWallet((Wallet) person.getWallet().clone());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return person;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", wallet=" + wallet +
'}';
}
}
public class AnnotationTest {
@Test
public void test() throws CloneNotSupportedException {
Person person = new Person("changlu", 18,new Wallet());
Person clonePerson = (Person) person.clone();
//使用clone()获得的对象与原先的引用地址不一样,是在堆中新开辟的
System.out.println(person == clonePerson);//false
System.out.println(person);
System.out.println(clonePerson);
//在Person的clone()方法中对克隆之后的person的属性wallet再次进行clone()
System.out.println(person.getWallet() == clonePerson.getWallet());//false
System.out.println(person);
System.out.println(clonePerson);
System.out.println(person.getName() == clonePerson.getName());//true
}
}
- 实现
Person
的深拷贝,关键点就是其中的引用数据类型Wallet
,那么我们就在Wallet
类中实现Cloneable
接口,并重写clone()
方法即可。 - 主要改动点就是上面例子中的1、2部分。
- 其中的
String
类型并没有进行深拷贝。
弊端说明:很明显我们就能感受到其中的问题所在,若是一个类中有多个自定义的引用类型,那么我们不得一个个类都要去实现Cloneable
接口,并重写方法吗?那么我们可以使用反序列化来实现深拷贝;对于jdk中原本定义好的核心类例如String无法进行深拷贝。
方式二:使用反序列化方式
import org.junit.Test;
import java.io.*;
/**
* @ClassName Test
* @Author ChangLu
* @Date 2021/2/21 20:48
* @Description TODO
*/
class Wallet implements Serializable{
private static final long serialVersionUID = -6849794470754688710L;
private Integer money = 100;
@Override
public String toString() {
return "Wallet{" +
"money=" + money +
'}';
}
}
class Person implements Serializable {
private static final long serialVersionUID = -6849794470754667722L;
private String name;
private int age;
private Wallet wallet;
public Person(String name, int age,Wallet wallet) {
this.name = name;
this.age = age;
this.wallet = wallet;
}
public String getName() {
return name;
}
public void setWallet(Wallet wallet) {
this.wallet = wallet;
}
public Wallet getWallet() {
return wallet;
}
//进行深拷贝
public Person deepClone() {
Person person = null;
ObjectInputStream ois = null;
ObjectOutputStream oops = null;
//使用对象输出流进行写操作,写入本身实例
try{
//对象输入流将本实例写入
ByteArrayOutputStream baos = new ByteArrayOutputStream();
oops = new ObjectOutputStream(baos);
oops.writeObject(this);
//对象输出流将实例读出
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());//读入刚刚写入的baos
ois = new ObjectInputStream(bais);
//对象输出流读出实例
person = (Person) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}finally {
if(ois != null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(oops != null){
try {
oops.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return person;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", wallet=" + wallet +
'}';
}
}
public class AnnotationTest {
@Test
public void test() {
Person person = new Person("changlu", 18, new Wallet());
Person clonePerson = person.deepClone();
//判断克隆的实例是否指向一个引用
System.out.println(person == clonePerson);//false
System.out.println(person);
System.out.println(clonePerson);
//判断克隆的实例中的引用对象是否指向一个引用
System.out.println(person.getWallet() == clonePerson.getWallet());//false
System.out.println(person);
System.out.println(clonePerson);
System.out.println(person.getName() == clonePerson.getName());
}
}
- Person与Wallet类都实现了
Serializable
接口,并且各自类中添加UID。 - 接着在
Person
中实现deepClone()
方法,该方法中对本身实例进行序列化与反序列化,从而达到深拷贝的效果。
Person
类中的String
引用类型也进行了深拷贝。
注意:使用反序列化进行深拷贝的对类实现Serializable
接口,并添加UID
。
总结
- 若是拷贝的类中仅仅是一些基本类型,那么直接使用
clone()
方法。 - 若是有多个自定义类或其他没有实现
Cloneable
接口的jdk核心类,那么建议使用反序列化的方式。 - 使用默认的
clone()
方法速度最快。
参考文章
[1]. 详解Java中clone的写法
[2]. JAVA 中的的clone()详解
[3]. Java 浅拷贝和深拷贝的理解和实现方式
- 点赞
- 收藏
- 关注作者
评论(0)