Java实例对象的Clone以及equals方法重写

实例对象的clone

对于八种基本数据类型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方法。

猜你喜欢

转载自blog.csdn.net/qq_26564827/article/details/80531676