Javaの浅いクローンと深いクローンの理解

6045 ワード

クローンコンセプト
Javaはすべてオブジェクトで、クローンはオブジェクトのクローンです.クローンは少し高度に聞こえるかもしれませんが、オブジェクトのコピーやオブジェクトのコピーもできます.普段開発中、オブジェクトコピーはいつ必要ですか?エンティティクラスがあり、多くのプロパティがあり、多くのプロパティに値が割り当てられている場合は、このオブジェクトを変更する必要がありますが、後で元の値が使用され、オブジェクトのコピーが必要です.
浅いクローン
コードで栗を挙げます.
public static class C implements Cloneable{
    String name;

    @Override
    public String toString() {
        return "C{" +
                "name='" + name + '\'' +
                '}';
    }

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

@Test
public void test2() throws CloneNotSupportedException {
    C c = new C();
    c.name = "Cat";
    System.out.println(c + " hashCode: " + c.hashCode());
    System.out.println(c.name + " hashCode: " + c.name.hashCode());

    C copy = (C) c.clone();
    System.out.println(copy + " hashCode: " + copy.hashCode());
    System.out.println(copy.name + " hashCode: " + copy.name.hashCode());

    c.name = "Dog";
    System.out.println(c + " hashCode: " + c.hashCode());
    System.out.println(copy + " hashCode: " + copy.hashCode());
}

test2          ,    :
C{name='Cat'} hashCode: 458209687
Cat hashCode: 67510
C{name='Cat'} hashCode: 233530418
Cat hashCode: 67510
C{name='Dog'} hashCode: 458209687
C{name='Cat'} hashCode: 233530418


clone()はObjectのメソッドで、サブクラスはCloneableインタフェースを実装する必要があり、呼び出しを実装しないとCloneNotSupportedException異常が放出されます.まずクラスCを定義し、cにはメンバー変数nameがあり、Stringタイプである.test 2メソッドでは、まずインスタンスcを作成し、インスタンスのnameにCatを割り当て、cとc.nameのhashCodeを印刷します.次に、cでインスタンスをクローンしてcopyに値を割り当て、copyとcopy.nameのhashCodeを承諾し、cとcopyのhashCodeを比較すると、cとcopyのhashCodeが異なることがわかり、スタックメモリに2つの領域があることを示します.c.nameとcopy.nameを比較すると、hashCodeは同じであることがわかります.次にc.nameで値を再割り当てし、Dogに値を付けます.印刷されたlogを見ても、copyの里name属性には影響しません.
もう一つの栗を見てみましょう.
public static class A implements Cloneable{
    B b;

    @Override
    public String toString() {
        return "A{" +
                "b=" + b +
                '}';
    }

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

public static class B implements Cloneable{
    String name;

    @Override
    public String toString() {
        return "B{" +
                "name=" + name +
                '}';
    }
}

@Test
public void test() throws CloneNotSupportedException {
    A a = new A();
    B b = new B();
    b.name = "Cat";
    a.b = b;
    System.out.println(a + " hashCode: " + a.hashCode());
    System.out.println(a.b + " hashCode: " + a.b.hashCode());

    A copy = (A) a.clone();
    System.out.println(copy + " hashCode: " + copy.hashCode());
    System.out.println(copy.b + " hashCode: " + copy.b.hashCode());

    b.name = "Dog";
    System.out.println(a + " hashCode: " + a.hashCode());
    System.out.println(copy + " hashCode: " + copy.hashCode());
}


     :
A{b=B{name=Cat}} hashCode: 458209687
B{name=Cat} hashCode: 233530418
A{b=B{name=Cat}} hashCode: 683287027
B{name=Cat} hashCode: 233530418
A{b=B{name=Dog}} hashCode: 458209687
A{b=B{name=Dog}} hashCode: 683287027


前の例ではクラスCにStringのメンバー変数があり、この例ではクラスAにカスタムクラスBのメンバー変数がありますが、Aインスタンスを作成し、Aインスタンスをコピーした後、aメンバー変数bのnameをDogに変更し、ログcopyのbのnameも変更されています.Dogに変更する前に、aとcopyのhashCodeは異なり、スタックメモリに2つの領域がある異なる2つのインスタンスを指していることを示しています.a.bとcopy.bのhashCodeは同じで、スタックメモリの同じ領域を指しているので、a.b.nameの値を変更すると、copy.b.nameも変更されるに違いありません.
Objectのcloneメソッドは浅いクローンにすぎず,最初の例ではc.name="Dog"はc.name=new String("Dog")と同等であり,c.nameの参照指向は既に変化しており,このときc.nameとcopy.nameは2つの異なるインスタンスを指している.第2の例では、a.bとcopy.bは常に同じ参照であり、B b 1=new B()に変更される場合、a.b = b1; b 1.name=「Dog」は、最初の例と同じです.
浅いクローンは、クローンされたクラスのメンバー変数が基本データ型であり、2つのデータを実現することができます.クローンされたクラスのメンバー変数がオブジェクトタイプである場合、このメンバー変数は元の参照であり、新しいオブジェクトの値に変更され、古いオブジェクトのオブジェクトタイプのメンバー変数は変化します.
深いクローン
深いクローン、クローンされたクラスのメンバー変数はどんなタイプでも、2つのデータを実現することができます.
深いクローンには、主に2つの方法があります.
  • clone書き換え方法
  • シーケンス化と逆シーケンス化
  • cloneメソッドの書き換え
    cloneメソッドを書き換えて深いクローンを実現するのは面倒で、オブジェクトタイプのすべてのメンバー変数に対して、インスタンスを再作成し、値を再割り当てします.コレクションクラスがより面倒な場合、例えばArrayList、ArrayListはclone()を書き換えたが、浅いクローンである場合、深いクローンを実現するにはすべてのModelを遍歴し、インスタンスを作成し、値を再割り当てする必要があります.
    次のコードは、上の浅いクローンの2文字目を深くクローン化し、クラスAのcloneを書き換える方法です.
    public static class A implements Cloneable{
        B b;
    
        @Override
        public String toString() {
            return "A{" +
                    "b=" + b +
                    '}';
        }
    
        @Override
        public Object clone() throws CloneNotSupportedException {
            A a = (A) super.clone();
            B b = new B();
            b.name = this.b.name;
            a.b = b;
            return a;
        }
    }
    
    

    シーケンス化と逆シーケンス化
    シーケンス化と逆シーケンス化は深いクローンを実現する方法が多く、インスタンスをjsonの文字列に変換し、文字列をインスタンスに戻すことができ、コピーに相当します.ObjectOutputStreamとObjectInputStreamを実現することもできます.アンドロイドでは、Parcelable実装などが利用できます.この方式はcloneメソッドの書き換えよりも実現が簡単である可能性があるが,性能的にはかなり劣る.
    次に、ObjectOutputStreamとObjectInputStreamを用いて簡単に実装します.コードは以下のとおりです.
    static   T copy(T  origin) {
        ByteArrayInputStream in = null;
        ByteArrayOutputStream pos = null;
        T copyList = null;
        try {
            pos = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(pos);
            out.writeObject(origin);
            in = new ByteArrayInputStream(pos.toByteArray());
            ObjectInputStream oin = new ObjectInputStream(in);
            copyList = (T) oin.readObject();
        } catch (Exception ignored) {
        }finally {
            if(in != null){
                try {
                    in.close();
                } catch (IOException ignored) {
                }
            }
            if(pos != null){
                try {
                    pos.close();
                } catch (IOException ignored) {
                }
            }
        }
        return copyList;
    }