浅いコピーと深いコピー(javaのcloneについて)

16879 ワード

cloneはその名の通りコピーであり、Java言語ではcloneメソッドがオブジェクトに呼び出されるため、オブジェクトがコピーされます.レプリケーション・オブジェクトとは、まずソース・オブジェクトと同じサイズの空間を割り当て、この空間に新しいオブジェクトを作成します.ではjava言語では、オブジェクトを作成する方法はいくつありますか?1.newオペレータを使用してオブジェクトを作成する2.cloneメソッドを使用してオブジェクトをコピーすると、この2つの方法はどのように同じで異なるのでしょうか.newオペレータの本意はメモリの割り当てです.プログラムがnewオペレータに実行されると、まずnewオペレータの後ろのタイプを見て、タイプが分かってから、どれだけのメモリ領域を割り当てるかを知ることができます.メモリを割り当てた後、コンストラクション関数を呼び出し、オブジェクトの各ドメインを塗りつぶすステップをオブジェクトの初期化と呼び、コンストラクションメソッドが戻った後、1つのオブジェクトが作成され、彼の参照(アドレス)を外部にパブリッシュすることができ、外部でこの参照を使用してこのオブジェクトを操作することができます.cloneは最初のステップでnewと似ています.メモリを割り当て、cloneメソッドを呼び出すと、割り当てられたメモリとソースオブジェクトが割り当てられます.(つまりcloneメソッドを呼び出すオブジェクト)同じで、元のオブジェクトの対応する各ドメインを使用して、新しいオブジェクトのドメインを埋めます.埋め込みが完了すると、cloneメソッドが戻り、新しい同じオブジェクトが作成され、同じようにこの新しいオブジェクトの参照を外部に公開できます.【Javaのcloneメソッドの詳細】
参照コピー
    //    
    private static void copyReferenceObject(){
        Person p = new Person(23, "zhang");
        Person p1 = p;
        System.out.println(p);
        System.out.println(p1);
    }

ここで印刷した結果:com.yaolong.clone.Person@3654919e com.yaolong.clone.Person@3654919e印刷の結果は同じであることがわかります.つまり、両者の参照は同じオブジェクトであり、新しいオブジェクトは作成されていません.したがって、リファレンス・コピーとオブジェクト・コピーの違いを区別するには、次に説明するオブジェクト・コピーについて説明します.
浅いコピー
浅いコピーは、元のオブジェクトのプロパティ値の正確なコピーを持つ新しいオブジェクトを作成します.属性が基本タイプである場合、コピーされるのは基本タイプの値です.プロパティがメモリアドレス(リファレンスタイプ)の場合、コピーはメモリアドレスです.そのため、オブジェクトの1つがこのアドレスを変更すると、別のオブジェクトに影響します.
オブジェクトコピーを実装するクラスは、Cloneableインタフェースを実装し、clone()メソッドを上書きする必要があります.Persionクラス:
package com.yaolong.clone;

public class Person implements Cloneable{

    //private Integer age;
    private int age;//       pojo             ,      

    private String name;

    public Person(Integer age, String name) {
        super();
        this.age = age;
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return super.toString();
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
    //    
    private static void copyRealObject() throws CloneNotSupportedException{
        Person p = new Person(23, "zhang");
        Person p1 = (Person) p.clone();      
        System.out.println(p);
        System.out.println(p1);
    }

ここで印刷した結果:com.yaolong.clone.Person@28084850 com.yaolong.clone.Person@37c390b8両者のオブジェクトアドレスが異なるため,コピーが実現されていることがわかる.
しかし、PersonクラスにStringタイプの参照オブジェクトnameがあり、本当にコピーされたのか、それとも同じnameオブジェクトを参照しているのかという問題があります.上のコードに基づいて、印刷を続けます.
        System.out.println("pName:"+p.getName().hashCode());
        System.out.println("p1Name:"+p1.getName().hashCode());

ここで印刷された結果:pName:15864556 p 1 Name:15864556が表示され、両方のnameプロパティは同じオブジェクトを指しています.基本データ型には参照の問題がないため、age属性をint基本タイプに変更しました.これは実際には典型的な浅いコピーです.
深いコピーメモリ:
以上より,Objectから継承されたcloneのデフォルトでは浅いコピーが実現されていることが分かる.
深いコピー
深いコピーは、すべてのプロパティをコピーし、プロパティが指す動的に割り当てられたメモリをコピーします.オブジェクトが参照されているオブジェクトと一緒にコピーされると、深いコピーが発生します.深いコピーは、浅いコピーに比べて速度が遅く、コストがかかります.
次に、cloneオブジェクトを深くコピーするには、Clonableインタフェースを使用してcloneメソッドを上書きして実装し、親クラスのcloneメソッドを呼び出して新しいオブジェクトを得るだけでなく、そのクラスの参照変数もcloneします.Objectのデフォルトのcloneメソッドのみを使用する場合は、浅いコピーです.
static class Body implements Cloneable{
    public Head head;

    public Body() {}

    public Body(Head head) {this.head = head;}

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

}
static class Head /*implements Cloneable*/{
    public  Face face;

    public Head() {}
    public Head(Face face){this.face = face;}

} 
public static void main(String[] args) throws CloneNotSupportedException {

    Body body = new Body(new Head());

    Body body1 = (Body) body.clone();

    System.out.println("body == body1 : " + (body == body1) );

    System.out.println("body.head == body1.head : " +  (body.head == body1.head));


}

以上のコードでは,BodyとFaceの2つの主要なクラスがあり,Bodyクラスでは1つのFaceオブジェクトが結合されている.Bodyオブジェクトをcloneすると、その組み合わせのFaceオブジェクトは浅いコピーのみを行います.印刷結果は、この結論を検証することができる:body==body 1:false body.head == body1.head : true
Bodyオブジェクトをclone時に深くコピーする場合は、Bodyのcloneメソッドでソースオブジェクトが参照するHeadオブジェクトもclone 1部にします.
static class Body implements Cloneable{
    public Head head;
    public Body() {}
    public Body(Head head) {this.head = head;}

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Body newBody =  (Body) super.clone();
        newBody.head = (Head) head.clone();
        return newBody;
    }

}
static class Head implements Cloneable{
    public  Face face;

    public Head() {}
    public Head(Face face){this.face = face;}
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
} 
public static void main(String[] args) throws CloneNotSupportedException {

    Body body = new Body(new Head());

    Body body1 = (Body) body.clone();

    System.out.println("body == body1 : " + (body == body1) );

    System.out.println("body.head == body1.head : " +  (body.head == body1.head));


}

印刷結果:body==body 1:false body.head == body1.head : false
このことから,bodyとbody 1内のhead参照は異なるHeadオブジェクトを指し,すなわちclone Bodyオブジェクトと同時に参照したHeadオブジェクトもコピーし,深いコピーを行っている.
本当に深いコピーですか
前節では、オブジェクトを深くコピーするには、Cloneableインタフェースを実装し、cloneメソッドを実装する必要があり、cloneメソッドの内部で、そのオブジェクトを参照する他のオブジェクトもcloneする必要があると結論しました.これにより、この引用されたオブジェクトもCloneableインタフェースを実装し、cloneメソッドを実装する必要があります.では、上記の結論から、BodyクラスはHeadクラスを組み合わせ、HeadクラスはFaceクラスを組み合わせ、Bodyクラスを深くコピーするにはBodyクラスのcloneメソッドでHeadクラスもコピーしなければならないが、Headクラスをコピーする場合、デフォルトでは浅いコピーが実行され、つまりHeadで組み合わせたFaceオブジェクトはコピーされない.検証コードは以下の通りです.(ここでは本来Faceクラスのコードだけを提示すればよいのですが、一貫性を持って読むためにコンテキスト情報を失わないようにするか、プログラム全体を提示するか、プログラム全体も非常に短いです)
static class Body implements Cloneable{
    public Head head;
    public Body() {}
    public Body(Head head) {this.head = head;}

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Body newBody =  (Body) super.clone();
        newBody.head = (Head) head.clone();
        return newBody;
    }

}

static class Head implements Cloneable{
    public  Face face;

    public Head() {}
    public Head(Face face){this.face = face;}
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
} 

static class Face{}

public static void main(String[] args) throws CloneNotSupportedException {

    Body body = new Body(new Head(new Face()));

    Body body1 = (Body) body.clone();

    System.out.println("body == body1 : " + (body == body1) );

    System.out.println("body.head == body1.head : " +  (body.head == body1.head));

    System.out.println("body.head.face == body1.head.face : " +  (body.head.face == body1.head.face));


}

印刷結果:body==body 1:false body.head == body1.head : false body.head.face == body1.head.face : true
では、Bodyオブジェクトにとって、これは深いコピーですか?実は深いコピーと言えるはずですが、ボディオブジェクト内で参照されている他のオブジェクトに対して(現在はHeadのみ)はコピーされている.つまり、2つの独立したBodyオブジェクト内のhead参照は独立した2つのHeadオブジェクトを指している.しかし、これは2つのHeadオブジェクトにとって同じFaceオブジェクトを指している.これは、2つのBodyオブジェクトがまだ一定のつながりがあり、完全に独立していないことを示している.これは、徹底した深いコピーとは言えない.
徹底した深いコピーの方法
上記の例では、2つのBodyオブジェクトが完全に独立していることを保証するにはどうすればいいのでしょうか.Headオブジェクトをコピーする場合でも、Faceオブジェクトを1部コピーすればよい.これは、FaceクラスにもCloneableインタフェースを実装させ、cloneメソッドを実装させ、Headオブジェクトのcloneメソッドで参照されるFaceオブジェクトをコピーさせる必要がある.修正されたコードの一部は次のとおりです.
static class Head implements Cloneable{
    public  Face face;

    public Head() {}
    public Head(Face face){this.face = face;}
    @Override
    protected Object clone() throws CloneNotSupportedException {
        //return super.clone();
        Head newHead = (Head) super.clone();
        newHead.face = (Face) this.face.clone();
        return newHead;
    }
} 

static class Face implements Cloneable{
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

上記の例を再実行すると、body==body 1:false bodyとなる.head == body1.head : false body.head.face == body1.head.face : false
これは、2つのBodyが完全に独立しており、間接的に参照されたfaceオブジェクトがコピーされている、つまり独立したFaceオブジェクトが参照されているということです.
このように、Faceオブジェクトが他のオブジェクト、例えばMouthを参照している場合、処理されていない場合、Bodyオブジェクトがコピーされた後も1レベル1レベルの参照で同じMouthオブジェクトに参照されます.同様に、Bodyを参照チェーン上で完全に独立させるには、Mouthオブジェクトも明示的にコピーさせるしかありません.これにより、1つのオブジェクトをコピーするときに、コピーしたオブジェクトとソースオブジェクトを完全に独立させるには、参照チェーン上の各レベルのオブジェクトが明示的にコピーされるという結論が得られる.したがって、徹底的な深いコピーを作成することは非常に面倒であり、特に参照関係が非常に複雑な場合、または参照チェーンのあるレベルで第三者のオブジェクトを参照し、このオブジェクトがcloneメソッドを実装していない場合、その後のすべての参照オブジェクトが共有されます.たとえば、Headによって参照されるFaceクラスがサードパーティ製ライブラリ内のクラスであり、Cloneableインタフェースが実装されていない場合、Face以降のすべてのオブジェクトは、前後の2つのBodyオブジェクトによって共通に参照されます.Faceオブジェクトの内部にMouthオブジェクトが結合され、Mouthオブジェクトの内部にToothオブジェクトが結合されているとします.
cloneは通常のプロジェクトの開発では頻繁に使用されないかもしれませんが、深いコピーと浅いコピーを区別することでjavaメモリの構造と動作方法をより深く理解することができます.徹底した深いコピーについては,ほとんど実現不可能であり,原因は前節で説明した.深いコピーと完全な深いコピーは、可変オブジェクトを作成するときにプログラムに微妙な影響を与える可能性があり、私たちが作成した可変オブジェクトが本当に可変であるかどうかを決定する可能性があります.cloneの重要なアプリケーションの一つも、可変オブジェクトの作成に使用されます.
alibabaの仕様マニュアル
  • 【強制】基本データ型とパッケージデータ型の使用基準は以下の通りである:1)すべてのPOJOクラス属性はパッケージデータ型を使用しなければならない.2)RPCメソッドの戻り値とパラメータはパッケージデータ型を使用しなければならない.3)すべての局所変数は基本データ型を使用することを推奨する.説明:POJOクラス属性に初期値がないのは使用者に使用する必要があることを注意することである.その場合,自分で明示的に付与する必要があり,NPE問題や入庫検査は利用者が保証する.正例:データベースのクエリー結果はnullである可能性があります.自動解体のため、基本データ型でNPEリスクを受信します.反例:ある業務の取引報告書には成約総額の上昇・下落状況、すなわち正負x%が表示され、xは基本データ型であり、呼び出されたRPCサービスは、呼び出されて成功しなかった場合、デフォルト値が返され、ページには0%が表示され、これは合理的ではなく、中線に表示されるべきである-.したがって、パッケージデータ型null値は、リモートコールに失敗し、異常終了などの追加情報を表すことができる.
  • 【推奨】Objectのcloneメソッドを使用してオブジェクトをコピーすることを慎む.説明:オブジェクトのcloneメソッドのデフォルトは浅いコピーであり、深いコピーを実現するにはcloneメソッドを書き換えてプロパティオブジェクトのコピーを実現する必要がある.