java 内存拷贝函数_java提高(15)---java深浅拷贝

java提高(15)---java深浅拷贝

一、前言

为什么会有深浅拷贝这个概念?

我觉得主要跟JVM内存分配有关,对于基本数据类型,只存在栈内存,所以它的拷贝不存在深浅拷贝这个概念。而对于对象而言,一个对象的创建会在内存中分配两块空间,一个在栈内存存对象的引用指针,一个在堆内存存放对象。这个时候会有一个问题,你拷贝的只是这个引用指针还是拷贝两块内存一起拷贝,这个时候就会有深浅拷贝一说。

还有之前我认为Arrays.copyOf()是深度拷贝,亲测后发现原来它也是浅拷贝。下面进行具体说明。

二、数据类型

数据分为基本数据类型(int, boolean, double, byte, char等)和对象数据类型。

基本数据类型的特点:直接存储在栈(stack)中的数据.

引用数据类型的特点:在栈内存存储对象引用,真实的数据存放在堆内存里

引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

9f9d055a07e2c44d08a13b25ac3c8723.png

三、什么是浅拷贝和深拷贝

首先需要明白,深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。那先来看看浅拷贝和深拷贝的概念。

在 Java 中,除了基本数据类型(元类型)之外,还存在 类的实例对象 这个引用数据类型。而一般使用 =号做赋值操作的时候。对于基本数据类型,实际上是拷贝的它的值,但是对于对象而言,其实赋值的只是这个对象的引用,将原对象的引用传递过去,他们实际上还是指向的同一个对象。

浅拷贝:如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象。

深拷贝:在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量。

深拷贝和浅拷贝的示意图大致如下:

f57bd73d902a168433589c5c0613ef3b.png

d3a530f5dd6b3b73da7d746a1a8bcc5c.png

具体接下来代码演示。

四、代码演示

1、浅拷贝

Person

public class Person{

public String name;

public Integer age;

public String sex;

/**

* 提供get和set方法和全参构造函数

*/

}

Test

public static void main(String[] args) throws Exception{

Person person = new Person("小小",3,"女");

//将person值赋值给person1

Person person1 = person;

System.out.println(person);

System.out.println(person1);

person1.setName("小小她爸");

System.out.println("person 中 name为:"+person.getName());

System.out.println("person1 中 name为:"+person.getName());

}

查看运行结果

970e95f93767dc356b8408bd9fd8a564.png

从图片中我们可以很明显看出,它们指向的内存地址是一致的,同样我改变person1的属性值时发现person的属性值也改变了。

说明:对于对象用"=" 赋值 其实只是引用指针的复制,这两个引用还是指向同一个对象。

2、深拷贝

如果要实现深拷贝就会比较复杂点

Student

/**

* 如果对象要实现深拷贝 那么实体需要做两步

* 1、实体实现Cloneable接口

* 2、重写 clone()方法

*/

public class Student implements Cloneable{

public String name;

public Integer age;

public String sex;

//这也是个实体

public Address address;

/**

* 提供get和set方法和全参构造函数

*/

@Override

protected Object clone() throws CloneNotSupportedException{

return super.clone();

}

}

Test

public static void main(String[] args) throws Exception{

Student student = new Student("小小", 3, "女", null);

//将person值赋值给person1

Student student1 = (Student) student.clone();

System.out.println(student);

System.out.println(student1);

student1.setName("小小她爸");

System.out.println("person 中 name为:" + student.getName());

System.out.println("person1 中 name为:" + student1.getName());

}

8139de43da1b850261ecb39318cc0063.png

这里可以已经是两个不同的对象了。但是这里需要注意的是,如果对象中含有对象,这个对象还是浅拷贝。

Address

public class Address{

public String city;

public int phone;

/**

* 提供get和set方法和全参构造函数

*/

}

Test

public static void main(String[] args) throws Exception{

Address address = new Address("杭州", 1888888888);

Student student2 = new Student("小小", 3, "女", address);

//将person值赋值给person1

Student student3 = (Student) student2.clone();

address.setCity("北京天安门");

System.out.println("person2 中 city为:" + student2.getAddress().getCity());

System.out.println("person3 中 city为:" + student3.getAddress().getCity());

}

734c259b71752b27142018c0f0d9aad1.png

我们发现虽然Student是实现了深拷贝,但Address却还是浅拷贝,那如何让Adress也实现深拷贝呢。Address修改

public class Address implements Cloneable{

public String city;

public int phone;

/**

* 提供get和set方法和全参构造函数

*/

@Override

protected Object clone() throws CloneNotSupportedException{

return super.clone();

}

Student修改

//修改clone方法

@Override

protected Object clone() throws CloneNotSupportedException{

Student s = (Student) super.clone();

s.address = (Address) address.clone();

return s;

}

f55427dced7f7b638bc7e2cc52b68482.png

弊端: 这里我们Person 类只有一个 Address 引用类型,而 Address 类没有,所以我们只用重写 Address 类的clone 方法,但是如果 Address 类也存在一个引用类型,

那么我们也要重写其clone 方法,这样下去,有多少个引用类型,我们就要重写多少次,如果存在很多引用类型,那么代码量显然会很大,所以这种方法不太合适。

所以还有另一种实现深拷贝方法。

序列化实现深拷贝

//序列化实现深拷贝

public Object deepClone() throws Exception{

// 序列化

ByteArrayOutputStream bos = new ByteArrayOutputStream();

ObjectOutputStream oos = new ObjectOutputStream(bos);

oos.writeObject(this);

// 反序列化

ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());

ObjectInputStream ois = new ObjectInputStream(bis);

return ois.readObject();

}

//因为序列化产生的是两个完全独立的对象,所有无论嵌套多少个引用类型,序列化都是能实现深拷贝的。

五、Arrays.copyOf()

之前我误以为Arrays.copyOf()为深拷贝,那只是因为我用的是基本数据类型作为数组,而基本数据类型上面已经说过它没有深浅拷贝这个概念,可以把他理解成只有深拷贝。

public static void main(String[] args){

//1、基本数据类型

int[] a = {0, 1, 2, 3};

// Arrays.copyOf拷贝

int[] copy = Arrays.copyOf(a, a.length);

a[0] = 1;

System.out.println(Arrays.toString(copy));

System.out.println(Arrays.toString(a));

//2、对象数组

Student[] stuArr = {new Student("小小", 3, "女"),new Student("小小爸", 29, "男"),new Student("小小妈", 27, "女")};

// Arrays.copyOf拷贝

Student[] copyStuArr = Arrays.copyOf(stuArr, stuArr.length);

copyStuArr[0].setName("小小爷爷");

System.out.println(Arrays.toString(stuArr));

System.out.println(Arrays.toString(copyStuArr));

}

运行结果:

30d8eab0fe6be916160eedef68044659.png

可以明显看出,对于基本数据类型只有深拷贝,而对于数组对象而言,明显存在深浅拷贝,而且可以看出Arrays.copyOf()为浅拷贝。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值