您的位置:

深入理解Cloneable接口

一、什么是Cloneable接口

在Java中,Cloneable接口是一个空接口,主要是为了告诉编译器一个类可以被克隆(clone)。一个类如果需要支持克隆操作,必须实现Cloneable接口。

Cloneable接口中没有任何方法,它只是一个标识接口。所谓标识接口,就是不定义任何方法,只是作为一个标识,告诉编译器这个类具有某种特性。

二、如何实现Cloneable接口

要支持克隆操作,一个类需要重写Object类的clone方法,并声明为public。Cloneable接口只是作为一个标识,不实现任何方法。


public class Person implements Cloneable {
  private String name;
  private int age;

  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }

  @Override
  public Object clone() throws CloneNotSupportedException {
    return super.clone();
  }
}

三、克隆的两种方式

在Java中,克隆对象通常有两种方式:浅克隆(Shallow Copy)和深克隆(Deep Copy)。

3.1 浅克隆

浅克隆是指对于一个对象,仅仅克隆了它本身,而没克隆它所包含的其他对象。


public class Person implements Cloneable {
  // ...

  public Object clone() throws CloneNotSupportedException {
    return super.clone();
  }
}

public class Student implements Cloneable {
  private String name;
  private Person person;

  public Student(String name, Person person) {
    this.name = name;
    this.person = person;
  }

  public String getName() {
    return name;
  }

  public Person getPerson() {
    return person;
  }

  public void setPerson(Person person) {
    this.person = person;
  }

  @Override
  public Object clone() throws CloneNotSupportedException {
    return super.clone();
  }
}

public class Test {
  public static void main(String[] args) throws CloneNotSupportedException {
    Person p1 = new Person("Tom", 20);
    Student s1 = new Student("Jerry", p1);

    Student s2 = (Student) s1.clone();

    System.out.println(s1 == s2);
    System.out.println(s1.getPerson() == s2.getPerson());
  }
}

以上代码中,s1和s2都是Student类型的对象,它们是两个独立的对象。在克隆s1时,它的person成员变量是浅克隆的,也就是说,s2的person成员变量引用的是同一个Person对象。

3.2 深克隆

深克隆是指对于一个对象,既克隆了它本身,又克隆了它所包含的其他对象。


public class Person implements Cloneable {
  // ...

  public Object clone() throws CloneNotSupportedException {
    return super.clone();
  }
}

public class Student implements Cloneable {
  private String name;
  private Person person;

  public Student(String name, Person person) {
    this.name = name;
    this.person = person;
  }

  public String getName() {
    return name;
  }

  public Person getPerson() {
    return person;
  }

  public void setPerson(Person person) {
    this.person = person;
  }

  @Override
  public Object clone() throws CloneNotSupportedException {
    Student student = (Student) super.clone();
    student.person = (Person) person.clone();
    return student;
  }
}

public class Test {
  public static void main(String[] args) throws CloneNotSupportedException {
    Person p1 = new Person("Tom", 20);
    Student s1 = new Student("Jerry", p1);

    Student s2 = (Student) s1.clone();

    System.out.println(s1 == s2);
    System.out.println(s1.getPerson() == s2.getPerson());
  }
}

以上代码中,s1和s2也都是Student类型的对象,它们是两个独立的对象。在克隆s1时,它的person成员变量是深克隆的,也就是说,s2的person成员变量引用的是一个新的Person对象。

四、对象拷贝的注意事项

1. 即使一个类 implements Cloneable 接口,如果没有重写 Object 类的 clone 方法,也无法使用 clone 方法克隆该类的实例。

2. 如果一个类的字段都是基本类型,那么无论是浅克隆还是深克隆,都不会有什么问题;但是,如果一个类的字段是引用类型,就要注意深浅克隆的问题。

3. 在克隆方法中,应该首先调用 super.clone() 方法得到一个新的对象,然后再对需要克隆的对象进行处理。

4. 如果要深度克隆一个对象,那么这个对象中所有的引用类型的成员变量均需要深度克隆;如果只是浅克隆一个对象,那么这个对象中的成员变量的克隆方式就要看具体的业务需求。

五、总结

本文详细介绍了Java语言中的Cloneable接口,包括什么是Cloneable接口以及如何实现Cloneable接口,以及浅克隆和深克隆的两种方式。同时,也提到了对象拷贝的注意事项。

在实际开发中,使用克隆方式创建对象可以提高性能,但需要注意克隆方式的选择,同时也需要考虑对象拷贝的深度和注意事项。