Object.Cloneの代わりにコピーコンストラクターを使用してディープコピーへの適切な方法
-
11-10-2019 - |
質問
object.cloneを使用してディープコピーを実行するコードがいくつかありますが、より「許容できる」コピーコンストラクター手法を使用して書き直そうとしています。以下は、私がやろうとしていることの2つの簡単な例です。1つ目はクローンを使用し、2つ目はコピーコンストラクターを使用しています。
クローンを使用したディープコピー
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でのクローニングの悪についてのすべての話にもかかわらず、クローンの代替品にはより少ないコードとより少ないキャストが必要であるということです(少なくともこの場合)。
Copy Constructorの代替案に関するフィードバックをありがとう。違ったやり方でやりますか?ありがとう。
解決
それ以外の:
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");
}
推奨事項:クローンを使用して、同一のコピーのみが必要な場合は、発信者が特定のタイプのインスタンスを要求したい場合は、コピーコンストラクタを使用してください。
他のヒント
に注意してください Person.deepCopy
コピーコンストラクターアプローチの Person
クラスは、すべてのサブクラスを明示的にテストする必要があります。これは基本的な設計、コードメンテナンス、テストの問題です。誰かが新しいサブクラスを導入した場合、クローニングの成功を妨げます Person
, 、忘れているか、更新できない Person.deepCopy
. 。 .clone()
メソッドは、仮想メソッドを提供することにより、この問題を回避します(clone
).
クローンベースのアプローチの利点の1つは、適切に実装された場合、クローン化された場合に特別な動作を必要としない派生タイプが特別なクローンコードを必要としないことです。ちなみに、私は、クローニング方法を公開するクラスは一般に継承可能ではないと考えがちです。代わりに、ベースクラスは保護された方法としてのクローニングをサポートする必要があり、派生クラスはインターフェイスを介したクローニングをサポートする必要があります。オブジェクトがクローニングをサポートしない場合、クローンAPIから例外をスローするべきではありません。代わりに、オブジェクトにはクローンAPIを持たないはずです。