クローン可能なJavaについて


95

Javaについて説明しているチュートリアルを探してCloneableいましたが、適切なリンクが得られず、スタックオーバーフローがいずれにせよより明確な選択肢になりつつあります。

以下について知りたいのですが。

  1. CloneableCloneableインターフェースを実装することで、オブジェクトのクローンまたはコピーを持つことができることを意味します。それを行うことの利点と欠点は何ですか?
  2. オブジェクトが複合オブジェクトの場合、再帰的な複製はどのように行われますか?

2
何と比較した場合の利点と欠点?
Galactus

4
私はそれを、クラスがクローン可能であるという利点とそうでないとの利点を意味すると読んだ。他にどのように解釈できるかわかりません:S
allyourcode 2013年

回答:


159

最初に知っておくべきことCloneableは、使用しないことです。

クローンをCloneable正しく実装することは非常に難しく、その努力はそれに見合うものではありません。

その代わりに、apache-commons SerializationUtils(deep-clone)やBeanUtils(shallow-clone)などの他のオプションを使用するか、単にコピーコンストラクターを使用します。

を使用したクローン作成に関するJosh Blochのビューについては、ここ参照してくださいCloneable。これは、このアプローチの多くの欠点を説明しています。(Joshua BlochはSunの従業員であり、数多くのJava機能の開発を率いていました。)


1
私はブロッホの言葉をリンクしました(引用するのではなく)
Bozho

3
BlockはCloneableを使用しないと言っていることに注意してください。彼はクローンを使用しないと言っていません(または少なくとも私は使用しないことを望みます)。リフレクションを使用するSerializationUtilsやBeanUtilsのようなクラスよりもはるかに効率的である、簡単にクローンを実装する多くの方法があります。例については、以下の私の投稿を参照してください。
チャールズ

インターフェイスを定義するときにコピーコンストラクターに代わるものは何ですか?単にコピーメソッドを追加しますか?
benez

@ベネズ私はそう言うでしょう。java-8以降ではstatic、インターフェースにメソッドを含めることができるので、static WhatEverTheInterface copy(WhatEverTheInterface initial)?しかし、クローン中にオブジェクトからフィールドをコピーするので、これが何をもたらすのだろうと思いますが、インターフェイスはメソッドのみを定義します。説明する気?
ユージーン

40

Cloneable自体は、残念ながら単なるマーカーインターフェイスです。つまり、clone()メソッドを定義していません。

何が行われるかは、Cloneableを実装しないクラスに対してCloneNotSupportedExceptionをスローし、実装するクラスに対してメンバーごとの浅いコピーを実行する、保護されたObject.clone()メソッドの動作を変更することです。

これがあなたが探している振る舞いである場合でも、それを公開するために独自のclone()メソッドを実装する必要があります。

独自のclone()を実装する場合、考えられるのは、super.clone()によって作成されたオブジェクトから開始することです。これは、正しいクラスであることが保証されており、浅いコピーが何でもない場合に備えて、フィールドの追加を行います。あなたが欲しい。clone()からコンストラクターを呼び出すと、サブクラスが独自の追加の複製可能なロジックを追加する場合に継承が壊れるため、問題が発生します。この場合、super.clone()を呼び出すと、間違ったクラスのオブジェクトが取得されます。

このアプローチは、コンストラクターで定義される可能性のあるロジックをすべてバイパスします。

もう1つの問題は、clone()のオーバーライドを忘れたサブクラスは、デフォルトの浅いコピーを自動的に継承することです。これは、可変状態(ソースとコピーの間で共有される)の場合には望みのものとはおそらく異なります。

ほとんどの開発者はこれらの理由でCloneableを使用せず、代わりに単にコピーコンストラクターを実装します。

Cloneableの詳細と潜在的な落とし穴については、Joshua Bloch著「Effective Java」を強くお勧めします。


12
  1. クローニングは、コンストラクターなしでオブジェクトを構築するための超言語的な方法を呼び出します。
  2. クローンを作成するには、CloneNotSupportedExceptionを使用して何らかの方法で処理するか、クライアントコードを処理する必要があります。
  3. 利点は少なく、手動でコピーコンストラクタを記述する必要はありません。

したがって、Cloneableは慎重に使用してください。それはあなたがすべてを正しく行うために適用する必要がある努力と比較してあなたに十分な利点を与えません。


Bozhoが言ったように、Cloneableは使用しないでください。代わりに、コピーコンストラクタを使用してください。 javapractices.com/topic/TopicAction.do?Id=12
Bane

@Bane、クローンするオブジェクトのタイプがわからない場合、どのクラスのコピーコンストラクターを呼び出すかをどうやって知るのでしょうか。
Steve Kuo

@Steve:フォローしていません。オブジェクトのクローンを作成する場合は、そのタイプがすでにわかっていると思います-結局のところ、クローン作成を計画しているオブジェクトが手元にあるはずです。そして、オブジェクトが特定のタイプからより一般的なタイプにオブジェクトを失った状況がある場合、単純な「インスタンス」を使用してそれを評価できませんでしたか???
Bane、2011年

4
@Bane:すべてがタイプAから派生したオブジェクトのリストがあり、おそらく10の異なるタイプがあるとします。各オブジェクトのタイプがわからない。この場合、instanceofを使用するのは非常に悪い考えです。別のタイプを追加する場合、これを行うたびに、別のインスタンスのテストを追加する必要があります。また、派生クラスが別のパッケージに含まれていてアクセスできない場合はどうなりますか?クローニングは一般的なパターンです。はい、Javaの実装は適切ではありませんが、適切に動作する方法はたくさんあります。コピーコンストラクタは同等の操作ではありません。
チャールズ

@Charles:詳細な例がなく、この種の問題に対処する最近の経験がない場合、私はBlochに頼る必要があります。アイテム#11。長くて少し読みにくいですが、基本的には「できる限り複製できないようにしてください。コピーコンストラクターは友だちです」と書かれています。
ベイン

7

クローニングは基本的なプログラミングパラダイムです。Javaが多くの点でJavaを適切に実装していない可能性があるという事実は、クローン作成の必要性をまったく減らしません。また、機能するクローンは簡単に実装できますが、機能は浅くても、深くても、混合されていても機能します。関数にcloneという名前を使用して、必要に応じてCloneableを実装しないこともできます。

クラスA、B、Cがあり、BとCはAから派生しているとします。次のようなタイプAのオブジェクトのリストがあるとします。

ArrayList<A> list1;

これで、そのリストにはタイプA、B、またはCのオブジェクトを含めることができます。オブジェクトのタイプがわかりません。したがって、次のようにリストをコピーすることはできません。

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(new A(a));
}

オブジェクトが実際にタイプBまたはCである場合、正しいコピーが得られません。また、Aが抽象的である場合はどうなりますか?現在、一部の人々はこれを提案しています:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    if(a instanceof A) {
        list2.add(new A(a));
    } else if(a instanceof B) {
        list2.add(new B(a));
    } else if(a instanceof C) {
        list2.add(new C(a));
    }
}

これは非常に悪い考えです。新しい派生型を追加するとどうなりますか?BまたはCが別のパッケージにあり、このクラスでそれらにアクセスできない場合はどうなりますか?

あなたがしたいことはこれです:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(a.clone());
}

多くの人々が、クローンの基本的なJava実装に問題がある理由を指摘しています。しかし、それはこの方法を簡単に克服します:

クラスA:

public A clone() {
    return new A(this);
}

クラスB:

@Override
public B clone() {
    return new B(this);
}

クラスC:

@Override
public C clone() {
    return new C(this):
}

同じ関数名を使用するだけで、Cloneableを実装していません。それが気に入らない場合は、別の名前を付けてください。


別の答えであなたのコメントに返信した後、私はこれを見ました。私はあなたが今どこに行くのかわかりますが、2)1)OPはCloneableの使用について(クローンの一般的な概念ではなく)具体的に尋ねました、そして2)コピーコンストラクターを区別するためにここで少しヘアを分割していますクローニングの一般的な概念。ここで伝えるアイデアは有効ですが、根本ではコピーコンストラクタを使用しています。;)
ベイン

ここではあなたのアプローチに同意することを言いたいのですが、ユーザーに直接copy-constructorを呼び出させるのではなく、A#copyMethod()を含めることです。
ベイン2014

5

A)コピーコンストラクターに比べてクローンの利点はそれほど多くありません。おそらく最大のものは、まったく同じ動的型の新しいオブジェクトを作成する機能です(宣言された型がクローン可能で、パブリッククローンメソッドを持っていると想定)。

B)デフォルトのクローンは浅いコピーを作成し、クローンの実装によって変更されない限り、浅いコピーのままになります。これは、特にクラスに最終フィールドがある場合は難しい場合があります

Bozhoは正しいです、クローンを正しくするのは難しい場合があります。コピーコンストラクタ/ファクトリはほとんどのニーズに対応します。


0

Cloneableの欠点は何ですか?

コピーしているオブジェクトが構成を持っている場合、クローンは非常に危険です。クローンは浅いコピーを作成するため、この場合に考えられる以下の副作用について考慮する必要があります。

db関連の操作を処理するオブジェクトが1つあるとします。たとえば、そのオブジェクトにはConnection、プロパティの1つとしてオブジェクトがあります。

したがって、誰かがのクローンをoriginalObject作成すると、作成されているオブジェクトは、としましょうcloneObject。ここoriginalObjectcloneObjectで同じ参照を保持Connectionオブジェクトを。

オブジェクトをoriginalObject閉じたとしましょう。オブジェクトがオブジェクト間で共有され、実際にによって閉じられたため、は機能しなくなります。ConnectioncloneObjectconnectionoriginalObject

プロパティとしてIOStreamを持つオブジェクトを複製したい場合も同様の問題が発生する可能性があります。

オブジェクトが複合オブジェクトの場合、再帰的な複製はどのように行われますか?

Cloneableは浅いコピーを実行します。つまり、元のオブジェクトとクローンオブジェクトのデータは同じ参照/メモリを指します。反対に、ディープコピーの場合、元のオブジェクトのメモリからデータがクローンオブジェクトのメモリにコピーされます。


最後の段落は非常に混乱しています。Cloneableコピーは行いませんObject.clone。「元のオブジェクトのメモリからのデータは、クローンオブジェクトのメモリにコピーされます」というのは、まさに何をするかObject.cloneです。深いコピーを説明するには、参照オブジェクトのメモリについて話す必要があります。
aioobe
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.