乐趣区

关于java:工作3年了居然还搞不清楚Java的浅拷贝和深拷贝老板一顿痛批

今日分享开始啦,请大家多多指教~

明天给大家分享的是对于 Java 的深拷贝和浅拷贝,简略来说就是创立一个和已知对象截然不同的对象。可能日常编码过程中用得不多,然而这是一个面试常常会问的问题,而且理解深拷贝和浅拷贝的原理,对于 Java 中的所谓值传递或者援用传递将会有更深的了解。

1. 创建对象的 5 种形式

  • 通过 new 关键字

这是最罕用的一种形式,通过 new 关键字调用类的有参或无参构造方法来创建对象。

比方 Object obj = new Object();

  • 通过 Class 类的 newInstance() 办法

这种默认是调用类的无参构造方法创建对象。

比方 Person p2 = (Person) Class.forName(“com.ys.test.Person”).newInstance();

  • 通过 Constructor 类的 newInstance 办法

这和第二种办法相似,都是通过反射来实现。通过 java.lang.relect.Constructor 类的 newInstance() 办法指定某个结构器来创建对象。

Person p3 = (Person) Person.class.getConstructors()[0].newInstance();

实际上第二种办法利用 Class 的 newInstance() 办法创建对象,其外部调用还是 Constructor 的 newInstance() 办法。

  • 利用 Clone 办法

Clone 是 Object 类中的一个办法,通过 对象 A.clone() 办法会创立一个内容和对象 A 截然不同的对象 B,clone 克隆,顾名思义就是创立一个截然不同的对象进去。

Person p4 = (Person) p3.clone();

  • 反序列化

序列化是把堆内存中的 Java 对象数据,通过某种形式把对象存储到磁盘文件中或者传递给其余网络节点(在网络上传输)。而反序列化则是把磁盘文件中的对象数据或者把网络节点上的对象数据,复原成 Java 对象模型的过程。

2. Clone 办法

在 Object.class 类中,源码为:

protected native Object clone() throws CloneNotSupportedException;

这是一个用 native 关键字润饰的办法,不了解也没关系,只须要晓得用 native 润饰的办法就是通知操作系统,这个办法我不实现了,让操作系统去实现。具体怎么实现咱们不须要理解,只须要晓得 clone 办法的作用就是复制对象,产生一个新的对象。那么这个新的对象和原对象是什么关系呢?

3. 根本类型和援用类型

在 Java 中数据类型能够分为两大类:根本类型和援用类型。

根本类型也称为值类型,别离是字符类型 char,布尔类型 boolean 以及数值类型 byte、short、int、long、float、double。

援用类型则包含类、接口、数组、枚举等。

Java 将内存空间分为堆和栈。根本类型间接在栈中存储数值,而援用类型是将援用放在栈中,理论存储的值是放在堆中,通过栈中的援用指向堆中寄存的数据。

上图定义的 a 和 b 都是根本类型,其值是间接寄存在栈中的;而 c 和 d 是 String 申明的,这是一个援用类型,援用地址是寄存在 栈中,而后指向堆的内存空间。

上面 d = c;这条语句示意将 c 的援用赋值给 d,那么 c 和 d 将指向同一块堆内存空间。

4. 浅拷贝

在解说浅拷贝之前,咱们用一段代码来实现它。



下面的代码是一个咱们要进行赋值的原始类 Person。上面咱们产生一个 Person 对象,并调用其 clone 办法复制一个新的对象。

留神:调用对象的 clone 办法,必须要让类实现 Cloneable 接口,并且覆写 clone 办法。


首先看原始类 Person 实现 Cloneable 接口,并且覆写 clone 办法, 它还有三个属性,一个援用类型 String 定义的 pname,一个根本类型 int 定义的 page,还有一个援用类型 Address,这是一个自定义类,这个类也蕴含两个属性 pprovices 和 city。

接着看测试内容,首先咱们创立一个 Person 类的对象 p1,其 pname 为 zhangsan,page 为 21,地址类 Address 两个属性为 湖北省和武汉市。接着咱们调用 clone() 办法复制另一个对象 p2,接着打印这两个对象的内容。

从第 1 行和第 3 行打印后果:

p1:com.bowen.demo.demo008.method1.Person@5dfe23e8  
p2:com.bowen.demo.demo008.method1.Person@583fb274

能够看出这是两个不同的对象。

从第 5 行和第 6 行打印的对象内容看,原对象 p1 和克隆进去的对象 p2 内容完全相同。

代码中咱们只是更改了克隆对象 p2 的属性 Address 为湖北省荆州市(原对象 p1 是湖北省武汉市),然而从第 7 行和第 8 行打印后果来看,原对象 p1 和克隆对象 p2 的 Address 属性都被批改了。

也就是说对象 Person 的属性 Address,通过 clone 之后,其实只是复制了其援用,他们指向的还是同一块堆内存空间,当批改其中一个对象的属性 Address,另一个也会跟着变动。

浅拷贝:创立一个新对象,而后将以后对象的非动态字段复制到该新对象,如果字段是值类型的,那么对该字段执行复制;如果该字段是援用类型的话,则复制援用但不复制援用的对象。因而,原始对象及其正本援用同一个对象。

5. 深拷贝

弄清楚了浅拷贝,那么深拷贝就很容易了解了。

深拷贝:创立一个新对象,而后将以后对象的非动态字段复制到该新对象,无论该字段是值类型的还是援用类型,都复制独立的一份。当你批改其中一个对象的任何内容时,都不会影响另一个对象的内容。

那么该如何实现深拷贝呢?Object 类提供的 clone 是只能实现浅拷贝的。

6. 如何实现深拷贝?

深拷贝的原理咱们晓得了,就是要让原始对象和克隆之后的对象所具备的援用类型属性不是指向同一块堆内存,这里有两种实现思路。

办法一:让每个援用类型属性外部都重写 clone() 办法

既然援用类型不能实现深拷贝,那么咱们将每个援用类型都拆分为根本类型,别离进行浅拷贝。比方下面的例子,Person 类有一个援用类型 Address(其实 String 也是援用类型,然而 String 类型有点非凡,前面会具体解说),咱们在 Address 类外部也重写 clone 办法。如下:

Address.class:

Person.class 的 clone() 办法:

@Override
    protected Object clone() throws CloneNotSupportedException {Person p = (Person) super.clone();
        p.address = (Address) address.clone();
        return p;
    }

测试还是和下面一样,咱们会发现更改了 p2 对象的 Address 属性,p1 对象的 Address 属性并没有变动。

然而这种做法有个弊病,这里咱们 Person 类只有一个 Address 援用类型,而 Address 类没有,所以咱们只用重写 Address 类的 clone 办法,然而如果 Address 类也存在一个援用类型,那么咱们也要重写其 clone 办法,这样上来,有多少个援用类型,咱们就要重写多少次,如果存在很多援用类型,那么代码量显然会很大,所以这种办法不太适合。

办法二:利用序列化

序列化是将对象写到流中便于传输,而反序列化则是把对象从流中读取进去。这里写到流中的对象则是原始对象的一个拷贝,因为原始对象还存在 JVM 中,所以咱们能够利用对象的序列化产生克隆对象,而后通过反序列化获取这个对象。

留神每个须要序列化的类都要实现 Serializable 接口,如果有某个属性不须要序列化,能够将其申明为 transient,行将其排除在克隆属性之外。

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

小结

本篇文章咱们解说的是 Java 的深拷贝和浅拷贝,其实现形式正是通过调用 Object 类的 clone() 办法来实现。学习是无止境的,心愿我分享的内容能够给大家带来帮忙!

今日份分享已完结,请大家多多包涵和指导!

退出移动版