2010-11-16 2 views
7

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

Deep копия с использованием клона

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()); 
     } 
    } 
} 

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

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()); 
     } 
    } 
} 

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

Я буду благодарен за обратную связь по альтернативе конструктора копирования. Не могли бы вы поступить иначе? Благодарю.

ответ

3

Вместо:

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"); 
} 

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

1

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

+2

Забыть или не удалось обновить Person.deepCopy, не связанные с проблемой. То есть, вы можете столкнуться с очень похожими проблемами с альтернативой клона. Например, если кто-то создает новый «Администратор класса расширяет личность», ему придется не забудьте реализовать Administrator.clone (при условии, что копия по каждому полю не выполняет глубокую копию). Невозможно обновить Person.deepCopy можно обработать, переопределив его в подклассе. И да, вы должны помнить об этом, но опять же это проблема с альтернативой клона. – vocaro

1

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

Смежные вопросы