简单来说,浅拷贝对于基本数据类型,会拷贝他们的值,而对于引用类型,会拷贝他们的引用地址。
下图为浅拷贝。
而对于深拷贝来说,会对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容。
基本类型(8大基本类型):整形:byte(1字节)、short(2字节)、int(4字节)、long(8字节);浮点型:float(4字节,单精度浮点型),double(8字节,双精度浮点型);一个字符型:char(占用2字节);一个布尔型:boolean(可能占用4字节或1字节,如果是boolean变量则4字节,因为是用int存储,而如果是boolean数组,则每个boolean变量都是1字节,因为使用byte存储)
引用类型:类、接口、数组。
首先说一点,浅拷贝和深拷贝都可以使用Cloneable接口,并实现clone()
方法,在方法中直接调用super.clone
方法即可。我们创建一个Father类,其中包含一个String,一个int,以及一个对象。
class Father implements Cloneable{
Example example;
String s;
int a;
public Father(){
}
...
@Override
public Object clone(){
try {
return super.clone();
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
return null;
}
...
}
我们继续看效果,我们创建一个Father对象,同时通过clone方法浅拷贝一个Father对象。
public static void main(String[]args){
Father father = new Father();
father.setA(10);
father.setExample(new Example(100));
father.setS("aaa");
Father fatherb = (Father) father.clone();
System.out.println("father==fatherb? "+(father==fatherb));
System.out.println("father.equals(fatherb)? "+father.equals(fatherb));
father.setA(5);
father.example.setExa(50);
System.out.println("father a:"+father.a+",fatherb a:"+fatherb.a);
System.out.println("father example:"+father.example+",fatherb example"+fatherb.example);
father.setS("nnn");
System.out.println("father s:"+father.s+",fatherb s:"+fatherb.s);
}
结果:可以看到拷贝之后的对象是一个新对象,所以==
返回的是false。而equals返回true,我这里冲洗了下equals就是比较值是否相等。之后我们改变了father的int类型变量a,可以看到father中a变更为5,而fatherb中的a没有改变,由此可以证明浅拷贝对应基本类型是拷贝的值。然后我们将father的example进行更改(将example中的数据域进行更改),可以看到fatherb也跟着改变。因此浅拷贝对应引用类型拷贝的是引用。
最后,为什么同样是引用类型的String也会改变呢,因为String的引用是指向了常量池中的字符串,而我们创建新的字符串常量“sss”,并让father中的s指向它,而fatherb 的s还是指向“sss”,因此fatherb没有跟着fathera改变。
father==fatherb? false
father.equals(fatherb)? true
father a:5,fatherb a:10
father example:Example exa 50,fatherb exampleExample exa 50
father s:nnn,fatherb s:aaa
我们可以继续利用 clone() 方法,既然 clone() 方法,是我们来重写的,实际上我们可以对其内的引用类型的变量,再进行一次 clone()。
首先是Example类,我们需要让他也实现Cloneable接口。
class Example implements Cloneable{
...
@Override
public Object clone(){
try {
return super.clone();
}
catch (CloneNotSupportedException e)
{
e.printStackTrace();
}
return null;
}
}
我们要做的就是将Father类中的对象再通过clone
拷贝一次,并返回。
class Father implements Cloneable{
...
@Override
public Object clone(){
try {
Father fatherb = (Father) super.clone();
fatherb.example = (Example) this.example.clone();
return fatherb;
//return super.clone();
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
return null;
}
}
在复习一下equals的标准写法吧。注意equasl传入的是Object对象。
标准的四步:(1)直接用==
判断,是否直接是同一块地址;
(2)如果传入的对象为null,可以直接返回false;
(3)如果不是同一个类加载器加载的,就不可能相等。
(4)最后在比较属性(基本类型通过==
,引用类型使用equals
方法)
@Override
public boolean equals(Object o1){
//直接存在同一块地址,
if(o1==this)
return true;
//为空可以直接返回false
if(o1==null)
return false;
//class不匹配肯定不可能相等
if(o1.getClass()!=this.getClass())
return false;
Father o2 = (Father) o1;
return this.a==o2.a&&this.s.equals(o2.s)&&this.example.equals(example);
}