Javaでのディープコピー、シャローコピー、クローンの違いを明確にする必要がある
残念ながら、「浅いコピー」、「ディープコピー」、および「クローン」はすべて、かなり不明確な用語です。
Javaコンテキストでは、最初に「値のコピー」と「オブジェクトのコピー」を区別する必要があります。
int a = 1;
int b = a; // copying a value
int[] s = new int[]{42};
int[] t = s; // copying a value (the object reference for the array above)
StringBuffer sb = new StringBuffer("Hi mom");
// copying an object.
StringBuffer sb2 = new StringBuffer(sb);
つまり、型が参照型である変数への参照の割り当ては、値がオブジェクト参照である「値のコピー」です。オブジェクトをコピーするには、明示的にまたは内部でnew
を使用する必要があります。
オブジェクトの「浅い」コピーと「深い」コピーの場合。浅いコピーとは、通常、オブジェクトの1レベルのみをコピーすることを意味し、ディープコピーとは、通常、複数レベルをコピーすることを意味します。問題は、レベルによって何を意味するかを決定することです。このことを考慮:
public class Example {
public int foo;
public int[] bar;
public Example() { };
public Example(int foo, int[] bar) { this.foo = foo; this.bar = bar; };
}
Example eg1 = new Example(1, new int[]{1, 2});
Example eg2 = ...
通常の解釈では、eg1
の「浅い」コピーは、Example
が1で、foo
フィールドが元の配列と同じ配列を参照する新しいbar
オブジェクトになります。例えば.
Example eg2 = new Example(eg1.foo, eg1.bar);
eg1
の「深い」コピーの通常の解釈は、Example
が1に等しく、foo
フィールドがcopy of元の配列を参照する新しいbar
オブジェクトです。例えば.
Example eg2 = new Example(eg1.foo, Arrays.copy(eg1.bar));
(C/C++のバックグラウンドから来た人々mightは、参照割り当てによって浅いコピーが生成されると言います。しかし、それは通常Javaコンテキストでの浅いコピーとは異なります。 )
さらに2つの質問/不確実性の領域が存在します。
深さはどれくらいですか? 2つのレベルで停止しますか? 3つのレベル?接続されたオブジェクトのグラフ全体を意味しますか?
カプセル化されたデータ型についてはどうですか。例えば文字列?文字列は、実際には1つのオブジェクトではありません。実際、それはいくつかのスカラーフィールドと文字の配列への参照を持つ「オブジェクト」です。ただし、文字の配列はAPIによって完全に隠されています。それで、文字列のコピーについて話すとき、それを「浅い」コピーまたは「深い」コピーと呼ぶのは理にかなっていますか?それとも単にコピーと呼ぶべきでしょうか?
最後に、クローン。クローンは、すべてのクラス(および配列)に存在するメソッドで、一般にターゲットオブジェクトのコピーを生成すると考えられています。しかしながら:
このメソッドの仕様は、これが浅いコピーであるか深いコピーであるかを意図的に述べていません(それが意味のある区別であると仮定します)。
実際、この仕様では、クローンが新しいオブジェクトを生成することを明記していません。
javadoc の内容は次のとおりです。
"このオブジェクトのコピーを作成して返します。"コピー "の正確な意味は、オブジェクトのクラスによって異なります。一般的な目的は、オブジェクトxについて、式
x.clone() != x
が真であり、式x.clone().getClass() == x.getClass()
はtrueになりますが、これらは絶対要件ではありません。通常、x.clone().equals(x)
がtrueになる場合がありますが、これは絶対要件ではありません。 "
これは、一方の極ではクローンmightがターゲットオブジェクトであり、もう一方の極ではクローンmight notが元のオブジェクトと等しいことを意味していることに注意してください。そして、これはクローンがサポートされていることを前提としています。
要するに、クローンは潜在的にJavaクラスごとに異なる何かを意味します。
(@supercatがコメントで述べているように)Java clone()
メソッドが壊れていると主張する人もいます。しかし、正しい結論は、OOのコンテキストではクローンの概念が壊れていると思います。知る限りでは、すべてのオブジェクトタイプで一貫性があり、使用可能なクローンの統一モデルを開発することは不可能です。
「クローン」という用語は曖昧です(ただし、Javaクラスライブラリには Cloneable インターフェイスが含まれます)。ディープコピーまたはシャローコピーを参照できます。ディープ/シャローコピーはJavaに特に関連付けられていませんが、オブジェクトのコピー作成に関する一般的な概念であり、オブジェクトのメンバーもコピーされる方法を指します。
例として、人のクラスがあるとしましょう:
class Person {
String name;
List<String> emailAddresses
}
このクラスのオブジェクトをどのようにクローンしますか?浅いコピーを実行している場合は、名前をコピーして、新しいオブジェクトにemailAddresses
への参照を配置できます。ただし、emailAddresses
リストの内容を変更した場合、両方のコピーのリストを変更することになります(オブジェクト参照が機能する方法だからです)。
ディープコピーとは、すべてのメンバーを再帰的にコピーすることを意味するため、新しいList
を新しいPerson
に対して作成し、その内容を古いオブジェクトから新しいオブジェクトにコピーする必要があります。
上記の例は些細なことですが、ディープコピーとシャローコピーの違いは重要であり、特に誰かが後でどのように使用するかを知らずに汎用のクローンメソッドを事前に考案しようとする場合、アプリケーションに大きな影響を与えます。深いセマンティクスまたは浅いセマンティクスが必要な場合や、一部のメンバーを深くコピーし、他のメンバーはコピーしないハイブリッドがある場合があります。
「浅いコピー」と「深いコピー」という用語は少しあいまいです。 「メンバーごとのクローン」という用語と、「セマンティッククローン」と呼ぶものを使用することをお勧めします。オブジェクトの「メンバーごとのクローン」は、元のオブジェクトと同じランタイムタイプの新しいオブジェクトであり、すべてのフィールドに対して、システムは「newObject.field = oldObject.field」を効率的に実行します。ベースObject.Clone()は、メンバーごとのクローンを実行します。メンバーワイズクローニングは一般的に正しい 出発点 オブジェクトのクローンを作成しますが、ほとんどの場合、メンバーごとのクローンの後に「修正作業」が必要になります。多くの場合、最初に必要な修正を実行せずにメンバーごとのクローンを介して生成されたオブジェクトを使用しようとすると、クローンされたオブジェクトやその他のオブジェクトの破損など、悪いことが起こります。一部の人々は「浅いクローニング」という用語をメンバーワイズクローニングを指すために使用しますが、それがこの用語の唯一の使用ではありません。
「セマンティッククローン」とは、元のデータと同じデータを含むオブジェクトです。 型の観点から。調べるために、Array>とcountを含むBigListを検討してください。そのようなオブジェクトのセマンティックレベルのクローンは、メンバーごとのクローンを実行し、Array>を新しい配列に置き換え、新しいネストされた配列を作成し、すべてのTを元の配列から新しい配列にコピーします。 T自身のいかなる種類の深いクローニングも試みません。。皮肉なことに、クローン作成を「浅いクローン作成」と呼ぶ人もいれば、「深いクローン作成」と呼ぶ人もいます。正確に役立つ用語ではありません。
本当に深いクローン作成(すべての可変型を再帰的にコピーする)が便利な場合もありますが、そのようなアーキテクチャ向けに構成要素が設計されている型でのみ実行する必要があります。多くの場合、真に深いクローン作成は過剰であり、必要なのは、実際には可視コンテンツが別のオブジェクトと同じオブジェクトを参照するオブジェクト(つまり、セマンティックレベルのコピー)である場合に干渉する可能性があります。オブジェクトの可視コンテンツが他のオブジェクトから再帰的に派生する場合、セマンティックレベルのクローンは再帰的なディープクローンを意味しますが、可視コンテンツが単なる一般的なタイプである場合、コードは盲目的にすべてをクローンするべきではありませんディープクローン対応の可能性があるようです。