English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
java 深拷貫と浅拷貫のメカニズム詳細
概要:
Javaでは、コピーは深いコピーと浅いコピーの二種類に分けられます。Javaは公共のスーパークラスObjectでcloneという名前のメソッドを実装しており、このメソッドで生成される新しいオブジェクトは浅いコピーであり、自分自身で定義したcloneメソッドで生成されるのは深いコピーです。
(一)Objectのcloneメソッド
新しいオブジェクトをnewで作成し、それを参照する宣言を使って参照し、その後、前の宣言を使って参照する場合、最終的な結果は:これらの変数は同じオブジェクトを指し、一方が変更されると他方も全て変更されます。あるオブジェクトのコピーを作成し、このコピーとオブジェクトの各属性が完全に同じで、このコピーと元のオブジェクトは互いに関係がない場合、この時点でcloneメソッドを使用する必要があります。
package Clone; import java.util.Date; /** * * @author QuinnNorris * Javaの二つのコピー機構 */ public class Clone { /** * @param args * @throws CloneNotSupportedException */ public static void main(String[] args) throws CloneNotSupportedException { // TODO Auto-生成されたメソッドスタブ ClassA valA = new ClassA(1, "old", new Date()); // 新しいClassAオブジェクトを宣言します。ClassAの機能には特に注目しない必要はありません ClassA valB = valA; // valAを参照するオブジェクトをvalBに割り当てます valA.setObject("new"); // valAの値を変更すると、valBも変更されます。なぜなら、valAとvalBは同じオブジェクトを指しているからです valB = valA.clone();//cloneメソッドを通じてコピーを作成します } }
ClassAクラスのcloneメソッドに関するオーバーライド部分:
//必要なインターフェースを実装する必要があります public class ClassA implements Cloneable { public ClassA clone() throws CloneNotSupportedException { return (ClassA) super.clone();//親クラス(Object)のcloneメソッドを呼び出します } }
1.Objectクラスのcloneメソッドの使用方法
cloneメソッドの使用方法について、四つの法則をまとめました。共有しましょう:
2.protected修飾子を持つcloneメソッド
java.lang.Objectの中で、cloneメソッドをprotected修飾子で設定することは非常に特別なケースです。protectedの範囲は:パッケージ可視+継承可能です。その理由は、このメソッドが返却するのはクローンされたオブジェクトであり、cloneメソッドがクローンするタイプは未知であり、返却値のタイプを決定することができません。したがって、後裔が継承し、拡張しながら実装することを許可するためにprotectedタイプとして設定されています。
3.cloneメソッドの実装にはCloneableインターフェースを実装する必要があります
それでは、cloneメソッドをオーバーライドする際に、なぜCloneableインターフェースを実装する必要があるのでしょうか?実際には、CloneableインターフェースはJavaのマークインターフェースであり、マークインターフェースとは、メソッドや属性を持たないインターフェースで、その存在は情報を伝えるためにだけであり、xxx instanceof Cloneableと使用される際に判別ができるように作られています。Cloneableインターフェースの出現は、設計者がクローン処理を行うことを知らせるためにです。もしオブジェクトがクローンが必要であっても、Cloneableインターフェースを実装していない場合(実際には「実装」を「記述」に置き換えた方が正確です)、検証例外が発生します。
4.cloneメソッドの実装には親クラスのcloneを呼び出す必要があります
このオブジェクトと同じものを作成するためには、親クラスのcloneメソッドを使用する必要があります。親クラスも同様に、Objectのcloneメソッドに至るまで繰り返します。それでは、Objectのcloneメソッドは何のためにあるのでしょうか?APIには以下のように記載されています:
protected Object clone( ) throws CloneNotSupportedException
このオブジェクトのコピーを作成し、返却します。
「コピー」の正確な意味は、オブジェクトのクラスに依存する可能性があります。その目的は、オブジェクト x に対して、
表現:x.clone() != xもtrueです。
表現:x.clone().getClass() == x.getClass()もtrueです。
これらは必ずしも満たす必要はありません。
一般的には:
x.clone().equals(x)はtrueですが、これは必ずしも満たす必要はありません。
慣習上、返されるオブジェクトはsuper.cloneを呼び出して取得されるべきです。
クラスとその全ての超クラス(Objectを除く)がこの約束を守ると、x.clone().getClass() == x.getClass()が成立します。
上記はAPIにおけるcloneの一部の基本的な説明です。結論から言えば、spuer.clone()を適切に呼び出した場合、それはコピーされたオブジェクトを返します。実行時、Objectのclone()はコピーするオブジェクトを特定し、そのための空間を割り当て、オブジェクトをコピーします。このクローンオブジェクトでは、全ての属性がコピーされたオブジェクトの属性と同じで、これらの同じ属性は二種類に分類されます:
第一种 : 八大原始型と不可変なオブジェクト(例えばString)
第二种 : 他のクラスオブジェクト
この場合、cloneメソッドはその値を元のオブジェクトの値に設定しますが、問題はありません。しかし、もう一方では、cloneメソッドは単にコピーされた新しいオブジェクトの参照を元のオブジェクトの参照に指し、そのクラスオブジェクトは二つのオブジェクトで変更されます。そのため、この時点で深浅拷贝の概念が関与します。
(二)浅拷贝
浅拷贝:被コピーするオブジェクトの全ての変数は、元のオブジェクトと同じ値を持っており、他のオブジェクトへの参照はすべて元のオブジェクトを指します。言い換えれば、浅複製は考慮されるオブジェクトのみをコピーし、参照するオブジェクトをコピーしません。
例えば、クラスAには他のクラスBの型の変数があります。Aでclone関数をオーバーライドしてsuper.cloneを呼び出すとき、作成された新しいオブジェクトと元のオブジェクトのクラスBの型の変数は同じで、同じBの型の変数を指しています。AでBの変数に変更を加えた場合、新しいコピーされたオブジェクトのBの変数も同じ変更が加わります。
覚えておいてください、super.cloneを実装したcloneメソッドはすべて浅拷贝です。
(三)深拷贝
深拷贝:被コピーするオブジェクトの全ての変数は、元のオブジェクトと同じ値を持っており、他のオブジェクトを参照する変数を除きます。他のオブジェクトを参照する変数は、コピーされた新しいオブジェクトを指し、元の参照先のオブジェクトではなくなります。言い換えれば、深複製はコピーするオブジェクトが参照するオブジェクトすべてをコピーします。
一般的に言えば、浅いコピーの場合、最初は2本の線が存在しますが、最終的に他のクラスの変数がある場合、これらの2本の線は最終的に合わさり、その変数を指すために一つにまとまります。深いコピーは完全に2本の線であり、互いに影響を与えません。なぜなら、すべての内部の変数のオブジェクトを完全にコピーしているからです。
深いコピーを実装する際には、cloneメソッド内でこのクラスの他のクラスの変数のclone関数を多く呼び出す必要があります。
(四)シリアライゼーション深いコピー
フレームワークの中で、時にはcloneメソッドがオーバーライドされていないことがあります。その場合、オブジェクトをコピーする必要があるときどう操作するのでしょうか?答えは、私たちがよく使用するシリアライゼーションメソッド、Serializableインターフェースを実装することです。
他の方法を探して深いコピーを置き換えることは無理なことですが、伝統的な深いコピーを使用する場合、オブジェクトをコピーする際に無数の層を追ってすべてのオブジェクト変数をコピーすることになりますか?時間の消費はさておき、そのようなコードを書くだけで恐怖が漂います。シリアライゼーション深いコピーはこれらのうち比較的簡単な方法です。
オブジェクトをストリームに書き込むプロセスはシリアライゼーションプロセスですが、Javaプログラマの間では「冷凍」という言葉で非常に象徴的に呼ばれています;一方、オブジェクトをストリームから読み出すデシリアライゼーションプロセスは「解凍」という言葉で呼ばれています。ただし、ストリームに書き込まれるのはオブジェクトのコピーであり、元のオブジェクトはJVM内にまだ存在しているため、「塩漬けにする」のはオブジェクトのコピーだけであり、Javaの塩漬けは新鮮に戻すことができます。
これはインターネット上の専門的な説明であり、私もここで自慢げにしない。Java言語では、オブジェクトを深いコピーする際には、まずオブジェクトがSerializableインターフェースを実装し、そのオブジェクト(実際にはオブジェクトのコピー)をストリームに書き込む(塩漬けにする)、それからストリームから読み出す(塩漬けを新鮮に戻す)ことで、オブジェクトを再構築することができます。
public Object deepClone() { //オブジェクトの書き込み ByteArrayOutputStream bo = new ByteArrayOutputStream(); ObjectOutputStream oo = new ObjectOutputStream(bo); oo.writeObject(this); //オブジェクトの読み取り ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray()); ObjectInputStream oi = new ObjectInputStream(bi); return(oi.readObject()); }
この学院派のコードは複雑に見えますが、実際にはオブジェクトをストリームに放り込み、取り出すだけです。無数のcloneの分析や判断よりも、これが非常に簡単です。この前提条件は、オブジェクトおよびその内部で参照されるすべてのオブジェクトがシリアライズ可能であることです。そうでない場合は、シリアライズ不可能なオブジェクトがtransientに設定されているかどうかを慎重に確認する必要があります。
transient:Serilizableインターフェースを実装したオブジェクトは、シリアライズできます(シリアライズとは、javaコードをバイトシリアル形式で書き出すことです。上記のコードの最初の3行でオブジェクトに書き込まれます)。Javaのこのシリアライズモデルは、開発者に多くの利便性を提供し、シリアライズのプロセスに関心を払う必要はありません。このクラスがSerilizableインターフェースを実装している限り、そのすべての属性とメソッドは自動的にシリアライズされます。しかし、シリアライズに不要な属性がある場合、このキーワードを使用します。シリアライズするオブジェクトの際に、この属性がシリアライズ先に含まれないように、Serilizableインターフェースを実装し、シリアライズに不要な属性の前にtransientキーワードを追加する必要があります。
(五)まとめ
実際のアプリケーションでは、深いコピーと浅いコピーは単なる概念であり、どちらが優れているかは決まっていません。実際の作業に応じて、どのようにオブジェクトをコピーするかを決定する必要があります。データベース操作において、特定のテーブルを抽出する際に他のテーブルに影響を与えずに、浅いコピーを使用する必要があります。フレームワークのSerializableでは、時間がかかりますが、深いコピーは非常に重要です。
ご覧いただきありがとうございます。皆様のサポートに感謝します。
声明:この記事の内容はインターネットから収集され、著作権者に帰属します。インターネットユーザーによって自発的に貢献し、アップロードされた内容であり、このウェブサイトは所有権を持ちません。人工的な編集は行われていません。また、関連する法的責任を負いません。著作権侵害を疑う内容がある場合は、notice#wまでメールを送信してください。3codebox.com(メールを送信する際、#を@に置き換えてください。報告を行い、関連する証拠を提供してください。一旦確認がとりついたら、このサイトは即座に侵害疑いのコンテンツを削除します。)