在学习设计模式中的Prototype Patterns时,了解到Java的根类Object提供了一个clone()方法(Object是所有类的父类,其一共提供了11个方法,具体详解可以看这篇文章),可以很容易实现Prototype Patterns。但clone()方法有浅克隆和深克隆两种,其中涉及到了java基本数据类型与其他数据类型的值与地址的问题。
</br>
1. java的基本数据类型与引用数据类型
┏数值型━┳━整数型:byte、short、int、long
┏基本数据类型━┫ ┗━浮点型:float、double
┃ ┣字符型:char
数据类型╋ ┗布尔型:boolean
┃ ┏类(class)
┗引用数据类型━╋接口(interface)
┗数组(array)
Java中有两类数据类型:基本数据类型与引用数据类型,基本数据类型直接保存数据的值,而引用数据类型只保存指向存储其所表示的值的地址。以String与int为例,String是Java的一个类,属于引用数据类型,所以String其实相当于一个指针(当然指针只是借用C++里的说法,Java中是没有指针的),而int属于基本数据类型,其声明的变量直接存储值。
</br>
1.1简要说明
对于引用数据类型来说,当声明一个String类型的变量str1并为其赋字符串值"strvalue"时,会将该字符串值"strvalue"存储在字符串常量池中,如果有一个新的String类型的变量str2被声明并且同样为其赋值"strvalue"的话,str1与str2会指向同一个地址(即存储“strvalue”的物理空间),两者共用同一个物理空间。str1与str2的值其实是地址值。
而对于基本数据类型来说,如果令int num1 = 123,int num2 = 123,num1和num2是不共用一个物理空间的。即num1和num2分别有各自的物理空间来存储123。
</br>
1.2 用代码说明
首先要明白判断两字符串相等时String的"str1==str2"与"str1.equals(str2)"的区别
"=="返回true时代表str1和str2指向同一个物理空间。
equals返回true时代表str1和`str2所指向的物理空间中存储的String值相等,并不需要两者同时指向一个物理空间,如下图:

再跟据以下代码说明String与int的不同:
1 | public class test { |
输出:
1 | str1 == str2: true //说明str1与str2指向一物理空间 |
2. Java的根类Object的clone()方法
2.1 为什么要有clone()方法?
首先说明clone方法是用来干什么的,我们都知道一般要创建一个新的对象时,往往是通过new关键字实现的。
试想这样的一种情况,有一个类Chicken,在一开始时我们创建了一个Chicken对象chicken1,它有很多属性,如年龄、饥饿度、体重、性别等等一系列属性,这些属性会随着时间的变化而变化。
现在存在着这样的一种需求,我想要把chicken1复制很多份,每一份都是一个新的对象,这些对象的所有属性都要和chicken1一样。
那么这时候你可能会想,我可以先new出来很多新的Chicken对象,然后根据chicken1的属性对这些新的对象的属性进行一一赋值,但是你可能会发现,这个工作是很繁琐的,你要遍历Chicken里的每一个属性并对其一一赋值。
那么如何优化呢,这时候就需要用到clone()方法了,直接用Chicken chicken2 = (Chicken)chicken1.clone()可以得到一个与chicken1一样的新对象chicken2。
</br>
可能你还这样想过:
Chicken chicken2 = chicken1能到一个新对象吗
看这样一段代码:
1 | public class Chicken{ |
输出:
1 | chicken1指向的物理空间的地址:Chicken@16d3586 |
你会发现Chicken chicken2 = chicken1所得到的chicken2并不是一个新对象,它与chicken1指向同一个物理空间,更改chicken1时chicken2也会随之改变。
因此,要想得到两个一模一样的对象,只能通过new然后逐一对属性赋值和clone()两种方法来试现。
2.2 clone方法的具体用法
要想使用clone()方法,必须在类中实现Cloneable接口,如下:
1 | public class Chicken implements Cloneable{ |
到此,我们已经可以初步的用一下clone()方法了,如下:
1 | public class Chicken implements Cloneable{ |
输出:
1 | chicken1指向的物理空间的地址:Chicken@16d3586 |
可以发现,chicken1与chicken2指向的地址不同,即二者是两个对象,而且我们并没有对chicken2的age进行设置,但它的值却不是初始化的0而是与chicken1的age相同,为1。能够很好的说明,我们的clone()方法得到了实现。
但是,要想深入理解clone()方法,现在还不算完。
clone()方法分为浅克隆(浅拷贝)与深克隆(深拷贝)两种。
2.2.1 浅克隆
看下面的例子:
1 | public class Chicken implements Cloneable{ |
输出:
1 | chicken1 == chicken2 : false //chicken1与chicken2两者地址不同 |
通过上面的例子我们就可以看出来什么是浅克隆了,当执行clone()方法时,对于Chicken类里面的属性:
●如果其属于八个基本数据类型中的一个,例如Chicken中的age属性,那么chicken2的age与chicken1的age对应不同的物理空间;
●如果其属于引用数据类型(不属于八大基本数据类型的都是引用数据类型,如自定义的类、String、enum等),例如Chicken中的name属性,那么chicken2的name与chicken1的name共用一个物理空间,并没有真正的把name拷贝过去,但是不影响我们的使用。
2.2.1 深克隆
既然浅克隆不会为引用数据类型真正克隆一个物理空间,那么深克隆的概念就呼之欲出了,深克隆自然就是会为类中的引用数据类型的属性克隆一个新的物理空间。
那么该如何实现深克隆呢?还记得我们在上面的1.2的代码中写的如何真正拷贝一个不同地址的String的方法吗?那就是new一个String。
如下:
1 | public class Chicken implements Cloneable{ |
输出:
1 | chicken1 == chicken2 : false //chicken1与chicken2两者地址不同 |
这就是深克隆了,当然这里只提供了一个很简单的例子String,另外的还有如果属性有自定义的类的深克隆(假如Chicken中的属性里有一个我们自定义的Egg类),这时候就需要在Egg类中也提供clone()方法(而且要是深克隆)。更复杂的是如果Egg中的属性还有自定义的类,那么就要层层检查,保证每一层的类都要提供深克隆的clone()方法。这样是非常麻烦的,当然,我们只需要在理论上的理解即可,在实际应用中,我们几乎从不需要这样做。
End
PS:码了三个小时_(:з)∠)_,本来只想先码一点儿,但是一码就不想停下来。。。还有一大堆作业等着呢(ㄒoㄒ)
</pr>
</pr>
参考链接: