Javaの浅いクローンと深いクローンの理解
6045 ワード
クローンコンセプト
Javaはすべてオブジェクトで、クローンはオブジェクトのクローンです.クローンは少し高度に聞こえるかもしれませんが、オブジェクトのコピーやオブジェクトのコピーもできます.普段開発中、オブジェクトコピーはいつ必要ですか?エンティティクラスがあり、多くのプロパティがあり、多くのプロパティに値が割り当てられている場合は、このオブジェクトを変更する必要がありますが、後で元の値が使用され、オブジェクトのコピーが必要です.
浅いクローン
コードで栗を挙げます.
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属性には影響しません.
もう一つの栗を見てみましょう.
前の例ではクラス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を書き換える方法です.
シーケンス化と逆シーケンス化
シーケンス化と逆シーケンス化は深いクローンを実現する方法が多く、インスタンスをjsonの文字列に変換し、文字列をインスタンスに戻すことができ、コピーに相当します.ObjectOutputStreamとObjectInputStreamを実現することもできます.アンドロイドでは、Parcelable実装などが利用できます.この方式はcloneメソッドの書き換えよりも実現が簡単である可能性があるが,性能的にはかなり劣る.
次に、ObjectOutputStreamとObjectInputStreamを用いて簡単に実装します.コードは以下のとおりです.
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メソッドを書き換えて深いクローンを実現するのは面倒で、オブジェクトタイプのすべてのメンバー変数に対して、インスタンスを再作成し、値を再割り当てします.コレクションクラスがより面倒な場合、例えば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;
}