Правильный способ глубокого копирования с помощью конструктора копирования вместо Object.clone - PullRequest
7 голосов
/ 17 ноября 2010

У меня есть некоторый код, который выполняет глубокое копирование с использованием Object.clone, но я пытаюсь переписать его, используя более «приемлемый» метод конструктора копирования. Ниже приведены два простых примера того, что я пытаюсь сделать: первый - с использованием клона, а второй - с помощью конструктора копирования.

Глубокое копирование с использованием клона

 import java.util.*;

 abstract class Person implements Cloneable {
     String name;
     public Object clone() throws CloneNotSupportedException {
         return super.clone();
     }
 }

 class Teacher extends Person implements Cloneable {
     int courses;
     public String toString() { return name + ": courses=" + courses; }
 }

 class Student extends Person implements Cloneable {
     double gpa;
     public String toString() { return name + ": gpa=" + gpa; }
 }

 public class DeepCopy_Clone {
     private static List<Person> deepCopy(List<Person> people) throws CloneNotSupportedException {
         List<Person> copy = new ArrayList<Person>();
         for (Person person : people) {
             copy.add((Person)person.clone());
         }
         return copy;
     }

     public static void main(String[] args) throws CloneNotSupportedException {
         ArrayList<Person> people = new ArrayList<Person>();

         Teacher teacher = new Teacher();
         teacher.name = "Teacher";
         teacher.courses = 5;
         people.add(teacher);

         Student student = new Student();
         student.name = "Student";
         student.gpa = 4.0;
         people.add(student);

         List<Person> peopleCopy = deepCopy(people);

         // Invalidate the original data to prove a deep copy occurred
         teacher.name = null;
         teacher.courses = -1;
         student.name = null;
         student.gpa = -1;

         for (Person person : peopleCopy) {
             System.out.println(person.toString());
         }
     }
 }

Глубокое копирование с использованием конструктора копирования

 import java.util.*;

 abstract class Person {
     String name;
     public Person() {}
     public Person(Person other) {
         this.name = other.name;
     }
     public Person deepCopy() {
         if (this instanceof Teacher) {
             return new Teacher((Teacher)this);
         } else if (this instanceof Student) {
             return new Student((Student)this);
         }

         throw new Error("Unknown type of person");
     }
 }

 class Teacher extends Person {
     int courses;
     public Teacher() {}
     public Teacher(Teacher other) {
         super(other);
         this.courses = other.courses;
     }
     public String toString() { return name + ": courses=" + courses; }
 }

 class Student extends Person {
     double gpa;
     public Student() {}
     public Student(Student other) {
         super(other);
         this.gpa = other.gpa;
     }
     public String toString() { return name + ": gpa=" + gpa; }
 }

 public class DeepCopy_ConstructorAlternative {
     private static List<Person> deepCopy(List<Person> people) {
         List<Person> copy = new ArrayList<Person>();
         for (Person person : people) {
             copy.add(person.deepCopy());
         }
         return copy;
     }

     public static void main(String[] args) {
         ArrayList<Person> people = new ArrayList<Person>();

         Teacher teacher = new Teacher();
         teacher.name = "Teacher";
         teacher.courses = 5;
         people.add(teacher);

         Student student = new Student();
         student.name = "Student";
         student.gpa = 4.0;
         people.add(student);

         List<Person> peopleCopy = deepCopy(people);

         // Invalidate the original data to prove a deep copy occurred
         teacher.name = null;
         teacher.courses = -1;
         student.name = null;
         student.gpa = -1;

         for (Person person : peopleCopy) {
             System.out.println(person.toString());
         }
     }
 }

Что мне кажется интересным, так это то, что, несмотря на все разговоры о пороках клонирования в Java, альтернатива клонам требует меньше кода и меньше приведения (по крайней мере, в данном конкретном случае).

Буду признателен за отзыв об альтернативе конструктора копирования. Вы бы сделали это по-другому? Спасибо.

Ответы [ 3 ]

3 голосов
/ 17 ноября 2010

Вместо:

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

Я бы предпочел:

public Person clone() {
    try {
        return (Person) clone();
    } catch (CloneNotSupportedException e) {
        throw new RuntimeException("This should be impossible ...");
    }
}

чтобы вызывающие абоненты не обрабатывали исключение, которое никогда не может произойти, и не должны вызывать.

В подходе копирования-конструктора переключение типов лучше обрабатывается полиморфно:

abstract class Person {
    ...
    public abstract Person deepCopy();
}

class Student {
    ...
    public Student deepCopy() {
        return new Student(this);
    }
}

class Teacher {
    ...
    public Teacher deepCopy() {
        return new Teacher(this);
    }
}

теперь компилятор может проверить, что вы предоставили глубокую копию для всех подтипов, и вам не нужно ничего приводить.

Наконец, обратите внимание, что подходы клонирования и конструктора копирования имеют одинаковые общедоступные API (независимо от того, называется ли метод clone() или deepCopy(), значения не имеет), поэтому используемый вами подход - деталь реализации. Подход конструктора копирования более многословен, поскольку вы предоставляете и конструктор, и метод, вызывающий этот конструктор, но его легче обобщить для общего средства преобразования типов, что позволяет использовать такие вещи, как:

public Teacher(Person p) {
    ...
    say("Yay, I got a job");
}

Рекомендация: используйте clone, если вам нужна только идентичная копия, используйте конструкторы копирования, если вызывающая сторона может запросить экземпляр определенного типа.

1 голос
/ 23 ноября 2010

Одним из преимуществ подхода, основанного на клонировании, является то, что при правильной реализации производные типы, которые сами по себе не требуют особого поведения, когда клонируется, не требуют специального кода клонирования.Между прочим, я склонен думать, что классы, которые предоставляют метод клонирования, как правило, не должны наследоваться;вместо этого базовый класс должен поддерживать клонирование как защищенный метод, а производный класс должен поддерживать клонирование через интерфейс.Если объект не поддерживает клонирование, он не должен выдавать исключение из API-интерфейса Clone;вместо этого у объекта не должно быть API-интерфейса клона.

1 голос
/ 17 ноября 2010

Обратите внимание, что в Person.deepCopy подхода конструктора копирования класс Person должен явно проверять все свои подклассы. Это фундаментальная проблема разработки, сопровождения кода и тестирования: она помешает успешному клонированию, если кто-то введет новый подкласс Person, забудет или не сможет обновить Person.deepCopy. Метод .clone() позволяет избежать этой проблемы, предоставляя виртуальный метод (clone).

...