Java -克隆实现方式 深克隆与潜克隆

快速介绍克隆

克隆从字面上看就是复制嘛,我们平时复制一个值类型数据直接复制就好了,但是复制一个引用类型比如对象的时候就无从下手了,因为赋值只能赋引用,而如果手动操作把里面的值取出赋给新对象又太麻烦耗时了,有没有好办法?java提供了clone。


克隆类型

在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于潜克隆复制的时候如果是值类型则直接克隆,而如果是引用类型则不会克隆对象引用的对象,而只是简单地复制这个引用。也就是说如果修改克隆后的对象中的引用类型数据,原对象中也会更改,因为都是指向同一个内存。而深克隆则都会克隆。

浅克隆
1. 被复制的类需要实现Clonenable接口, 该接口为标记接口(不含任何方法)
2. 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。
3. 将得到的复制对象返回

class Student implements Cloneable{  
    private int number;  

    public int getNumber() {  
        return number;  
    }  

    public void setNumber(int number) {  
        this.number = number;  
    }  

    @Override  
    public Object clone() {  
        Student stu = null;  
        try{  
            stu = (Student)super.clone();  
        }catch(CloneNotSupportedException e) {  
            e.printStackTrace();  
        }  
        return stu;  
    }  
}  

深克隆
前面两步跟浅克隆差不多后面一步不同
1. 被复制的类需要实现Clonenable接口, 该接口为标记接口(不含任何方法)
2. 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。
3. 将得到的复制对象返回,(深克隆做法)如果对象中有引用对象那么对引用对象再克隆一次。

class Student implements Cloneable{  
    private int number;  
    private ArrayList<String> image = new ArrayList<String>();
    public int getNumber() {  
        return number;  
    }  
   public int getImage() {  
        return image;  
    } 
    public void setNumber(int number) {  
        this.number = number;  
    }  
       public void setImage(String url) {  
        this.image.add(url);  
    } 
    @Override  
    public Object clone() {  
        Student stu = null;  
        try{  
            stu = (Student)super.clone();  
            stu.number=this.number;
            //引用对象再次克隆
            stu.image=this.image.clone();
        }catch(CloneNotSupportedException e) {  
            e.printStackTrace();  
        }  
        return stu;  
    }  
}  

当然这种深克隆方式有缺陷,如果引用对象有很多,或者说引用套引用很多重,比如image是个对象,对象里面又有引用对象,那么太麻烦了

解决多层克隆问题
用序列化的方式来实现对象的深克隆,其实如果你懂序列化的话就很容易理解了,就是把前面clone方法中的简单复制序列化后再返回。具体步骤就是先把对象序列化,转换为二进制码,然后再反序列化成对象,最后赋值。从而实现克隆

public class Outer implements Serializable{
    private static final long serialVersionUID = 369285298572941L;  //最好是显式声明ID
    public Inner inner;
   //Discription:[深度复制方法,需要对象及对象所有的对象属性都实现序列化] 
    public Outer myclone() {
        Outer outer = null;
        try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
           oos.writeObject(this);
       // 将流序列化成对象
           ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
           ObjectInputStream ois = new ObjectInputStream(bais);
           outer = (Outer) ois.readObject();
       } catch (IOException e) {
           e.printStackTrace();
      } catch (ClassNotFoundException e) {
           e.printStackTrace();
       }
       return outer;
   }
 }

里面clone类中用到的inner类也必须实现Serializable 否则无法序列化

public class Inner implements Serializable{
 2   private static final long serialVersionUID = 872390113109L; //最好是显式声明ID
 3   public String name = "";
 4 
 5   public Inner(String name) {
 6       this.name = name;
 7   }
 8 
 9   @Override
10   public String toString() {
11       return "Inner的name值为:" + name;
12   }
13 }

而且基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。

总结

具体用那种克隆方式因实际情况而定,每种方法都有他的优缺点,适用情况也不同,使用序列化的话相比较前面两种是比较消耗内存的,但是更好的解决了多层克隆问题,如果实际场景中需要克隆的对象都是值类型就使用浅克隆即可,如果含有单个引用对象如上面的数组之类的,那么简单的深克隆即可,如果多层则使用序列化方式。

猜你喜欢

转载自blog.csdn.net/HJsir/article/details/80270193