zoukankan      html  css  js  c++  java
  • Java 之 clone 方法(对象拷贝)

    一、对象的克隆(拷贝)

      克隆的对象包含一些已经修改过的属性,而 new 出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠克隆方法了。

    二、克隆分类

      1、克隆对象前提

    protected native Object clone() throws CloneNotSupportedException;
    

        该方法被native修饰,告诉 JVM 自己去调用。当我们在自定义类中使用该方法的时候,需要继承一个 Cloneable 接口,否则会抛出无法克隆的异常。该方法是一个浅复制,不是深复制。

      2、浅拷贝

           浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。

      3、深拷贝

        深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。

    三、浅拷贝的实现

      拷贝的前提:实现一个 Cloneable 接口,该接口只是一个标记接口,里面并没有具体的方法。

    public interface Cloneable {}
      实现方式:实现 Cloneable 接口并重写 Object 类中的 clone() 方法

      声明一个 Worker 类,其中有一个name成员变量:

     1 //克隆的对象必须实现Cloneable这个接口,而且需要重写clone方法
     2 class Worker implements Cloneable{
     3     private String name;
     4 
     5     public Worker(String name) {
     6         super();
     7         this.name = name;
     8     }
     9 
    10     public Worker() {
    11         super();
    12     }
    13     public String getName() {
    14         return name;
    15     }
    16     public void setName(String name) {
    17         this.name = name;
    18     }
    19     @Override
    20     public String toString() {
    21         return "Worker [name=" + name + "]";
    22     }
    23     @Override
    24     protected Object clone() throws CloneNotSupportedException {
    25         return super.clone();
    26     }
    27     
    28 }
    29 
    30 public static void main(String[] args) throws Exception {
    31         
    32         Worker worker1 = new Worker("张三");
    33         
    34         Worker worker2 = (Worker) worker1.clone();
    35         
    36         System.out.println(worker1.toString());
    37         System.out.println(worker2.toString());
    38         
    39         System.out.println("==============");
    40         //克隆后得到的是一个新的对象,所以重新写的是student2这个对象的值
    41         worker2.setName("李四");
    42         
    43         System.out.println(worker1.toString());
    44         System.out.println(worker2.toString());
    45         
    46     }
    47  
    48  运行结果:
    49  Worker [name=张三]
    50  Worker [name=张三]
    51  ==============
    52  Worker [name=张三]
    53  Worker [name=李四]

      图解说明:

         

        此时,worker1 与 worker2 是两个不同的对象,修改 worker2的值并不影响 worker1.

          注意:

        

        这是只是进行赋值操作,用两个指针指向同一个对象,并不是复制对象!

       上面的 Demo 中 Worker 只有一个基本类型的成员变量,如果再添加一个引用类型的成员变量呢?

        如果在 Worker 类中添加一个 Address 属性呢?
     1 //如果在 Worker 类中添加一个 Address 属性呢?
     2 //克隆的对象必须实现Cloneable这个接口,而且需要重写clone方法
     3 class Worker2 implements Cloneable{
     4     private String name;
     5     
     6     private Address addr;
     7 
     8 
     9     public Worker2(String name, Address addr) {
    10         super();
    11         this.name = name;
    12         this.addr = addr;
    13     }
    14 
    15     public Worker2() {
    16         super();
    17     }
    18 
    19     public String getName() {
    20         return name;
    21     }
    22 
    23     public void setName(String name) {
    24         this.name = name;
    25     }
    26     
    27 
    28     public Address getAddr() {
    29         return addr;
    30     }
    31 
    32     public void setAddr(Address addr) {
    33         this.addr = addr;
    34     }
    35     @Override
    36     public String toString() {
    37         return "Worker2 [name=" + name + ", addr=" + addr + "]";
    38     }
    39 
    40     @Override
    41     protected Object clone() throws CloneNotSupportedException {
    42         return super.clone();
    43     }
    44 }
    45 
    46 class Address {
    47     private String city;
    48     
    49     
    50     public Address() {
    51         super();
    52     }
    53 
    54     public Address(String city) {
    55         super();
    56         this.city = city;
    57     }
    58 
    59     public String getCity() {
    60         return city;
    61     }
    62 
    63     public void setCity(String city) {
    64         this.city = city;
    65     }
    66 
    67     @Override
    68     public String toString() {
    69         return "Address [city=" + city + "]";
    70     }
    71 }
    72 //主方法
    73 public static void main(String[] args) throws Exception {
    74         
    75         Address addr = new Address("北京");
    76         Worker2 worker1 = new Worker2("张三", addr);
    77         
    78         Worker2 worker2 = (Worker2) worker1.clone();
    79         
    80         System.out.println(worker1.toString());
    81         System.out.println(worker2.toString());
    82         
    83         System.out.println("==============");
    84         //克隆后得到的是一个新的对象,所以重新写的是student2这个对象的值
    85         worker2.setName("李四");
    86         
    87         addr.setCity("上海");
    88         
    89         System.out.println(worker1.toString());
    90         System.out.println(worker2.toString());
    91         
    92     }
    93  
    94  运行结果:
    95  Worker2 [name=张三, addr=Address [city=北京]]
    96 Worker2 [name=张三, addr=Address [city=北京]]
    97 ==============
    98 Worker2 [name=张三, addr=Address [city=上海]]
    99 Worker2 [name=李四, addr=Address [city=上海]]
      这时发现与我们想象的并不一样, Worker2 不是由 Worker1 复制的,与 Worker1 没有关系吗?
      这时由于进行的是浅复制,对于 引用数据类型来说,并没有进行复制。
      图解:
      
        对于浅克隆来说,复制 worker1 这个对象,只会复制基本数据类型的成员变量,而 Address 是一个引用数据类型的变量,它address 的指向还是 worker1 中的 address 对象,两个共用一个 Address 对象,所以当 worker1 修改 address 属性时,worker2 也会随着更改。

    四、深拷贝的实现

      1、方式一:手动依次实现 clone() 方法

      1 //克隆的对象必须实现Cloneable这个接口,而且需要重写clone方法
      2 class Worker3 implements Cloneable{
      3     private String name;
      4     
      5     private Address3 addr;
      6 
      7 
      8     public Worker3(String name, Address3 addr) {
      9         super();
     10         this.name = name;
     11         this.addr = addr;
     12     }
     13 
     14     public Worker3() {
     15         super();
     16     }
     17 
     18     public String getName() {
     19         return name;
     20     }
     21 
     22     public void setName(String name) {
     23         this.name = name;
     24     }
     25     
     26 
     27     public Address3 getAddr() {
     28         return addr;
     29     }
     30 
     31     public void setAddr(Address3 addr) {
     32         this.addr = addr;
     33     }
     34 
     35     
     36 
     37     @Override
     38     public String toString() {
     39         return "Worker3 [name=" + name + ", addr=" + addr + "]";
     40     }
     41 
     42 //    @Override
     43 //    protected Object clone() throws CloneNotSupportedException {
     44 //        
     45 //        Worker3 worker3 = (Worker3) super.clone();   //浅复制
     46 //        
     47 //        Address3 addr3 = (Address3) worker3.getAddr().clone(); //深复制
     48 //        
     49 //        worker3.setAddr(addr3);
     50 //        
     51 //        return worker3;
     52 //    }
     53     
     54     @Override
     55     protected Object clone() throws CloneNotSupportedException {
     56         
     57         Worker3 worker3 = null;
     58         
     59         worker3 = (Worker3) super.clone();   //浅复制
     60         
     61         //worker3.addr = (Address3) worker3.getAddr().clone(); //深复制
     62         worker3.addr = (Address3) addr.clone(); //深复制
     63         
     64         return worker3;
     65     }
     66 
     67 
     68     
     69 }
     70 
     71 class Address3 implements Cloneable{
     72     private String city;
     73     
     74     
     75     public Address3() {
     76         super();
     77     }
     78 
     79     public Address3(String city) {
     80         super();
     81         this.city = city;
     82     }
     83 
     84     public String getCity() {
     85         return city;
     86     }
     87 
     88     public void setCity(String city) {
     89         this.city = city;
     90     }
     91 
     92     @Override
     93     public String toString() {
     94         return "Address3 [city=" + city + "]";
     95     }
     96 
     97     @Override
     98     protected Object clone() throws CloneNotSupportedException {
     99         return super.clone();
    100     }
    101     
    102 }
    103 //主方法
    104 public static void main(String[] args) throws Exception {
    105         
    106         Address3 addr = new Address3("北京");
    107         Worker3 worker1 = new Worker3("张三", addr);
    108         
    109         Worker3 worker2 = (Worker3) worker1.clone();
    110         
    111         System.out.println(worker1.toString());
    112         System.out.println(worker2.toString());
    113         
    114         System.out.println("==============");
    115         //克隆后得到的是一个新的对象,所以重新写的是worker2这个对象的值
    116         worker2.setName("李四");
    117         
    118         addr.setCity("上海");
    119         
    120         System.out.println(worker1.toString());
    121         System.out.println(worker2.toString());
    122         
    123     }
    124  
    125  运行结果:
    126  Worker3 [name=张三, addr=Address3 [city=北京]]
    127 Worker3 [name=张三, addr=Address3 [city=北京]]
    128 ==============
    129 Worker3 [name=张三, addr=Address3 [city=上海]]
    130 Worker3 [name=李四, addr=Address3 [city=北京]]
        此时就实现了基本数据类型与引用数据类型全部进行了复制。

      2、方式二:实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

      1 class Student implements Serializable {
      2 
      3     private static final long serialVersionUID = 1L;
      4     
      5     private int age;
      6     private String name;
      7     private Teacher teacher;
      8     
      9     public int getAge() {
     10         return age;
     11     }
     12     public void setAge(int age) {
     13         this.age = age;
     14     }
     15     public String getName() {
     16         return name;
     17     }
     18     public void setName(String name) {
     19         this.name = name;
     20     }
     21     public Teacher getTeacher() {
     22         return teacher;
     23     }
     24     public void setTeacher(Teacher teacher) {
     25         this.teacher = teacher;
     26     }
     27     @Override
     28     public String toString() {
     29         return "Student [age=" + age + ", name=" + name + ", teacher=" + teacher + "]";
     30     }
     31     
     32     //使得序列化student3的时候也会将teacher序列化
     33     public Object deepCopt()throws Exception {
     34         
     35         ByteArrayOutputStream bos = new ByteArrayOutputStream();
     36         ObjectOutputStream  oos = new ObjectOutputStream(bos);
     37         oos.writeObject(this);
     38         //将当前这个对象写到一个输出流当中,,因为这个对象的类实现了Serializable这个接口,所以在这个类中
     39         //有一个引用,这个引用如果实现了序列化,那么这个也会写到这个输出流当中
     40         
     41         ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
     42         ObjectInputStream ois = new ObjectInputStream(bis);
     43         return ois.readObject();
     44         //这个就是将流中的东西读出类,读到一个对象流当中,这样就可以返回这两个对象的东西,实现深克隆
     45     }
     46 
     47 }
     48 
     49 class Teacher implements Serializable {
     50     
     51     private int age;
     52     private String name;
     53     
     54     public int getAge() {
     55         return age;
     56     }
     57     public void setAge(int age) {
     58         this.age = age;
     59     }
     60     public String getName() {
     61         return name;
     62     }
     63     public void setName(String name) {
     64         this.name = name;
     65     }
     66     @Override
     67     public String toString() {
     68         return "Teacher [age=" + age + ", name=" + name + "]";
     69     }
     70         
     71 }
     72 //主方法
     73 public static void main(String[] args) throws Exception {
     74         Teacher t1 = new Teacher();
     75         t1.setAge(33);
     76         t1.setName("王老师");
     77         
     78         Student stu1 = new Student();
     79         stu1.setAge(22);
     80         stu1.setName("张三");
     81         stu1.setTeacher(t1);
     82         
     83         Student stu2 = (Student) stu1.deepCopt();
     84         System.out.println(stu1);
     85         System.out.println(stu2);
     86         
     87         System.out.println("===============");
     88         
     89         stu2.getTeacher().setAge(44);
     90         stu2.getTeacher().setName("李老师");
     91         
     92         stu2.setAge(23);
     93         stu2.setName("李四");
     94         
     95         System.out.println(stu1);
     96         System.out.println(stu2);
     97         
     98     }
     99  
    100  运行结果:
    101  Student [age=22, name=张三, teacher=Teacher [age=33, name=王老师]]
    102 Student [age=22, name=张三, teacher=Teacher [age=33, name=王老师]]
    103 ===============
    104 Student [age=22, name=张三, teacher=Teacher [age=33, name=王老师]]
    105 Student [age=23, name=李四, teacher=Teacher [age=44, name=李老师]]
      序列化克隆:
        序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。
        利用serializable实现深复制(这个是利用Serializable,利用序列化的方式来实现深复制(深克隆),在其中利用了Io流的方式将这个对象写到IO流里面,然后在从IO流里面读取,这样就实现了一个复制,然后实现序列化的这个会将引用的那个对象也一并进行深复制,这样就实现了这个机制,同时在IO里面读取数据的时候还使用了装饰者模式)
        
      这种方式可以解决多层克隆问题:
    如果引用类型里面还包含很多引用类型,或者内层引用的类里面又包含引用类型,使用 clone() 方法就会很麻烦,这时就可以使用序列化和反序列化的方式实现对象的深克隆。

    五、封装克隆工具

      可以将对象克隆封装成一个工具类:
     1 import java.io.ByteArrayInputStream;
     2 import java.io.ByteArrayOutputStream;
     3 import java.io.ObjectInputStream;
     4 import java.io.ObjectOutputStream;
     5 import java.io.Serializable;
     6 
     7 public class CloneUtil {
     8  
     9     @SuppressWarnings("unchecked")
    10     public static <T extends Serializable> T clone(T object) throws Exception{
    11         //写入字节流
    12         ByteArrayOutputStream bout = new ByteArrayOutputStream();
    13         ObjectOutputStream oos = new ObjectOutputStream(bout);
    14         oos.writeObject(object);
    15  
    16         //分配内存,写入原始对象,生成新对象
    17         ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
    18         ObjectInputStream ois = new ObjectInputStream(bin);
    19         // 此处不需要释放资源,说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义
    20         // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放
    21         return (T) ois.readObject();
    22     }
    23 
    24 }

    六、总结

      实现对象克隆有两种方式:
        1. 实现Cloneable接口并重写Object类中的clone()方法;
        2. 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
  • 相关阅读:
    Caesar cipher
    遗传算法之背包问题
    Transport scheme NOT recognized: [stomp]
    error running git
    Canvas 旋转的图片
    canvas时钟
    火箭起飞
    让图标转起来
    Tomcat启动脚本
    Task中的异常处理
  • 原文地址:https://www.cnblogs.com/niujifei/p/13948752.html
Copyright © 2011-2022 走看看