Domanda

Ho qualche codice che esegue una copia profonda utilizzando Object.clone, ma sto cercando di riscrivere usando la tecnica più costruttore di copia "accettabile". Qui di seguito sono due semplici esempi di quello che sto cercando di fare, il primo clone utilizzando ed il secondo con un costruttore di copia.

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

copia completa utilizzando costruttore di copia

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

Quello che trovo interessante è che, nonostante tutti i discorsi sui mali della clonazione in Java, il clone alternativa richiede meno codice e un minor numero di calchi (in questo caso particolare, almeno).

sarei Apprezziamo feedback sulla copia costruttore di alternativa. Lo faresti in modo diverso? Grazie.

È stato utile?

Soluzione

Al posto di:

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

Io preferirei:

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

in modo da chiamanti non devono gestire un'eccezione che non può mai accadere, e non c'è bisogno di cast.

Nell'approccio costruttore di copia, la commutazione tipo è gestito meglio polimorfico:

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

Ora il compilatore può verificare che ci avete fornito copia completa per tutti i sottotipi, e non è necessario alcun calchi.

Infine, nota che sia la clonazione e costruttore di copia approccio hanno la stessa API pubblica (se il metodo viene chiamato clone() o deepCopy() non importa molto), in modo che ci si avvicina uso è un dettaglio di implementazione. L'approccio costruttore di copia è più prolisso quando si forniscono sia un costruttore e un metodo di chiamare quel costruttore, ma può essere più facilmente generalizzato a una struttura generale del tipo di conversione, permettendo cose come:

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

Raccomandazione:. Usare clone di se desideri solo una copia identica, uso di copia-costruttori se il chiamante potrebbe voler chiedere un'istanza di un tipo specifico

Altri suggerimenti

Si prega di notare che nel Person.deepCopy dell'approccio costruttore di copia, la classe Person deve prova per tutti i suoi sottoclassi in modo esplicito. Si tratta di un problema di progettazione fondamentale, la manutenzione del codice e test: si eviterebbe la clonazione di successo se qualcuno introduce una nuova sottoclasse di Person, dimenticando o essere in grado di aggiornamento Person.deepCopy. Il metodo .clone() evita questo problema fornendo un metodo virtuale (clone).

Uno dei vantaggi di un approccio clone-based è che, se correttamente attuato, tipi derivati ??che non si richiede un comportamento speciale quando clonato non richiederà codice speciale clonazione. Per inciso, tendo a pensare che classi che espongono un metodo di clonazione dovrebbero generalmente essere non ereditabile; invece, una classe base dovrebbe supporti clonazione come metodo protetto, e una classe derivata dovrebbe sostenere clonazione tramite un'interfaccia. Se un oggetto non supporta la clonazione, non dovrebbe generare un'eccezione da un'API Clone; invece, l'oggetto non dovrebbe avere un API clone.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top