对于八种基本数据类型clone则比较简单:
int m=10;
int n=m;
这样就可以实现基本数据类型的clone,但是实例对象就不可以,因为上述操作对象,只是两个变量都指向同一个对象,因此通过任何一个变量来修改对象,另一方都会察觉。
而有时候需要对一个实例对象进行Clone,用来保存其状态,那么就需要专门的操作来实现对象的Clone,这样Clone以后,对原有的实例对象进行修改,则不会影响Clone的对象,因此可以用Clone的对象来保存其当时的状态。
主要有两种方式:
》继承Cloneable接口并重写Object中clone()方法
》继承Serializable接口来表示可序列化,同时自己写一个clone方法,通过对象的序列化和反序列化来实现clone
重写clone方法:
/*
Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
The general intent is that, for any object x, the expression:
1) x.clone() != x will be true
2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
3) x.clone().equals(x) will be true, this is not an absolute requirement.
*/
protected native Object clone() throws CloneNotSupportedException;
-浅克隆
public class ShallowClone implements Cloneable {
String name="请clone我!";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone(){
// TODO Auto-generated method stub
ShallowClone SC=null;
try {
SC=(ShallowClone)super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return SC;
}
public static void main(String[] args) {
ShallowClone SC=new ShallowClone();
System.out.println("-----------原版-----------");
System.out.println(SC.getName());
ShallowClone SClone=(ShallowClone)SC.clone();
System.out.println("-----------克隆版-----------");
System.out.println(SClone.getName());
System.out.println("------区别是不是两个对象---------");
System.out.println("原版:"+SC.getName());
SClone.setName("我是Clone的!");
System.out.println("克隆版:"+SClone.getName());
System.out.println("----------两个对象是地址是否相等------------");
System.out.println(SC==SClone);
System.out.println("------------判断两个对象类类型是否相等-------------");
System.out.println(SC.getClass()==SClone.getClass());
System.out.println();
}
}
运行结果:
-----------原版-----------
请clone我!
-----------克隆版-----------
请clone我!
------区别是不是两个对象---------
原版:请clone我!
克隆版:我是Clone的!
----------两个对象是地址是否相等------------
false
------------判断两个对象类类型是否相等-------------
true
从上面可以看出,SClone是对象SC的clone,克隆对象与原对象的值一样,后面修改SClone的字段name值,后面输出可以发现只有SClone对象name修改了,而SC的那么并没有修改,说明两者并不是指向同一个对象。
后面的判断也验证了上面的说法:
用==判断两个对象的内存地址是否相等,结果false则表明不是同一个对象。
用SC.getClass()==SClone.getClass()判断两个类类型是否相等,则表明是相等的(文档中说非强制)。
(当然equals判断肯定是false,具体请看最后)
其实,这种clone有一个关键点需要说明:ShallowClone字段值都是java基本的数据类型,所以没有什么异常,但是如果类的对象中的字段属性值中也有对象,那么如果还是这种处理方法就会出现异常:类的其他基本类型的字段值都可以clone,但是内部字段对象属性则只是赋值引用,并没有实现clone,通过clone对象修改内部对象属性的状态时,原版的对象对象对应的内部对象属性也会跟着改变(两个对象的字段对象都指向同一个对象,字段对象并没有实现clone)
-深度clone
为了解决上述问题,则就需要进行深度clone
//内部字段对象
public class AddClass implements Cloneable {
int m;
public int getM() {
return m;
}
public void setM(int m) {
this.m = m;
}
@Override
protected Object clone() {
// TODO Auto-generated method stub
AddClass ac=null;
try {
ac=(AddClass) super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return ac;
}
}
public class DeepClone implements Cloneable {
AddClass addClass;
int number;
public AddClass getAddClass() {
return addClass;
}
public void setAddClass(AddClass addClass) {
this.addClass = addClass;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
protected Object clone() {
// TODO Auto-generated method stub
DeepClone dc=null;
try {
dc=(DeepClone) super.clone();
dc.addClass=(AddClass) addClass.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return dc;
}
public static void main(String[] args) {
DeepClone dc=new DeepClone();
AddClass ac= new AddClass();
dc.setAddClass(ac);
dc.setNumber(0);
ac.setM(0);
System.out.println("原版:number="+dc.getNumber()+" dc.addClass.m="+dc.getAddClass().getM());
DeepClone dclone=(DeepClone) dc.clone();
System.out.println("克隆版:number="+dclone.getNumber()+" dc.addClass.m="+dclone.getAddClass().getM());
System.out.println("-----------------------修改后-----------------------");
ac.setM(2);
dclone.setNumber(1);
System.out.println("原版:number="+dc.getNumber()+" dc.addClass.m="+dc.getAddClass().getM());
System.out.println("克隆版:number="+dclone.getNumber()+" dc.addClass.m="+dclone.getAddClass().getM());
}
}
运行结果:
原版:number=0 dc.addClass.m=0
克隆版:number=0 dc.addClass.m=0
-----------------------修改后-----------------------
原版:number=0 dc.addClass.m=2
克隆版:number=1 dc.addClass.m=0
要实现深度clone,则字段对象类要继承cloneable接口,同时重写clone方法,然后外部对象也要继承cloneable接口同时实现clone方法,不过这个clone方法需要手动调用字段对象的clone方法,这样可以实现深度clone。
输出结果看以看出,外部类对象和内部字段对象都实现了clone,并且修改原版的字段对象,并不会对clone的对象产生影响。
但是这样写虽然可以实现clone,如果对象嵌套字段对象,那么就会有很多需要写,因此这种方法就不适合了。就需要使用另一种方法:继承Serializable接口
通过继承Serializable接口,然后通过对象的序列化和反序列化实现深度clone
public class Other implements Serializable {
/**
*
*/
private static final long serialVersionUID = -3465331114779243637L;
String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
public class SDeepClone implements Serializable {
/**
*
*/
private static final long serialVersionUID = -5427637747572757487L;
int number;
Other other;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public Other getOther() {
return other;
}
public void setOther(Other other) {
this.other = other;
}
public SDeepClone myClone(){
SDeepClone sdc=null;
try {
ByteArrayOutputStream bos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis=new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bis);
sdc=(SDeepClone) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return sdc;
}
public static void main(String[] args) {
SDeepClone sdc=new SDeepClone();
Other other=new Other();
other.setValue("原");
sdc.setOther(other);
sdc.setNumber(0);
SDeepClone sdclone= sdc.myClone();
System.out.println("---------------clone--------------------");
System.out.println("原版: number="+sdc.getNumber()+" sdc.other.value="+sdc.getOther().getValue());
System.out.println("克隆版: number="+sdclone.getNumber()+" sdc.other.value="+sdclone.getOther().getValue());
other.setValue("修改");
System.out.println("---------------修改后--------------------");
System.out.println("原版: number="+sdc.getNumber()+" sdc.other.value="+sdc.getOther().getValue());
System.out.println("克隆版: number="+sdclone.getNumber()+" sdc.other.value="+sdclone.getOther().getValue());
}
}
运行结果:
---------------clone--------------------
原版: number=0 sdc.other.value=原
克隆版: number=0 sdc.other.value=原
---------------修改后--------------------
原版: number=0 sdc.other.value=修改
克隆版: number=0 sdc.other.value=原
只需要字段对象实现serializable接口,主类也实现serializable接口,然后写一个方法来对对象序列化和反序列化就可以实现深度clone。
下面顺便说一下既然clone对象,判断两个对象是否相等就不能用父类object中继承的equals方法了,因为Object中的equals中使用==则比较是内存地址,所以要重写equals方法和hashCode方法
为什么重写equals方法要也要重写hashCode方法,主要是因为用到HashSet或者HashMap、HashTable等这些是根据对象的hash值来用的,如果你自己定一个对象作为key,那么就要重写equals方法和hashCode方法。
比如:在HashMap中,如果你需要自定义对象作为Key,那么你不论通过key存储或者获取value,都需要计算key的hash值,如果你重写了equals方法,但没有重写hashCode方法(ObjectHashCode方法是对对象的内存地址进行hash值计算),所以会出现两个对象的值相等,但是hash值不相等。这样就会出现问题:HashMap中put元素的时候,计算key的hash值。然后找到对应的数组下标,然后在表头插入元素,如果两个对象相等,但hashCode值不等,则第一个元素对象作为Key放进hashMap中,则通过get(Key)获取value的时候,由于对象相等,但是hash值不等,所以会出现计算hash值找下标找的错误的下标,然后调用equals方法比较也会找不到对应的value。
同时对HashSet也会有致命影响,通过hash值来存储对象对象,如果两个对象相等,但是hash值不等,则会被存储在set中,这就与set初衷有冲突,就是不能存储重复的元素。
下面来重写上面第一个的equals方法和hashCode方法
public class ShallowClone implements Cloneable {
String name="请clone我!";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone(){
// TODO Auto-generated method stub
ShallowClone SC=null;
try {
SC=(ShallowClone)super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return SC;
}
@Override
public int hashCode() {
// TODO Auto-generated method stub
return name.hashCode();
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
if(this==obj){
return true;
}
if(obj==null){
return false;
}
if(this.getClass()!=obj.getClass()){
return false;
}
ShallowClone sc=(ShallowClone)obj;
if(name==null){
if(sc.name==null){
return true;
}else{
return false;
}
}
if((name.equals(sc.name))){
return true;
}
return false;
}
public static void main(String[] args) {
ShallowClone SC=new ShallowClone();
System.out.println("-----------原版-----------");
System.out.println(SC.getName());
ShallowClone SClone=(ShallowClone)SC.clone();
System.out.println("-----------克隆版-----------");
System.out.println(SClone.getName());
System.out.println("------区别是不是两个对象---------");
System.out.println("原版:"+SC.getName());
System.out.println("equals比较是否相等:"+SClone.equals(SC));
System.out.println("hash值比价:"+(SClone.hashCode()==SC.hashCode()));
}
}
运行结果:
-----------原版-----------
请clone我!
-----------克隆版-----------
请clone我!
------区别是不是两个对象---------
原版:请clone我!
equals比较是否相等:true
hash值比价:true
如果没有重写equals和hashCode方法,则会出现最后两个比较出现false。
重点可以看一下重写的equals方法,所以为了保证正确使用,重写equals方法,要重写hashCode方法。