Javaのコピー機構の詳しい説明

黄舟
リリース: 2017-03-01 11:45:56
オリジナル
1256 人が閲覧しました


Javaでは、コピーはディープコピーとシャローコピーの2種類に分けられます。 Java は public スーパークラス Object に clone というメソッドを実装します。このメソッドによって複製された新しいオブジェクトはシャロー コピーであり、自分で定義した clone メソッドはディープ コピーです。

(1) Object の Clone メソッド

新しいオブジェクトを作成し、1 つのステートメントを使用してそれを参照し、次に別のステートメントを使用して前のステートメントを参照すると、最終結果は次のようになります: これら 2 つのステートメント 変数は次のようになります。同じオブジェクトを指しているため、1 つの部分を変更すると、すべての部分が変更されます。オブジェクトのさまざまなプロパティとまったく同じオブジェクトのコピーを作成する必要があり、コピーの変更が元のオブジェクトとは何の関係もない場合は、この時点で 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-generated method stub

        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 メソッドのオーバーライド部分:

//需要实现Cloneable接口public class ClassA implements Cloneable {

    public ClassA clone() throws CloneNotSupportedException {        
    return (ClassA) super.clone();
    //调用父类(Object)的clone方法
    }

}
ログイン後にコピー

1. Object での clone メソッドの使用方法

clone メソッドの使用に関する 4 つのルールをまとめた人がいます:

  1. 対象 オブジェクトのコピーを取得するには、Object クラスの clone() メソッドを使用できます。

  2. 派生クラスで基本クラスの clone() メソッドをオーバーライドし、public として宣言します。

  3. 派生クラスの clone() メソッドで、 super.clone() を呼び出します。

  4. Cloneable インターフェイスを派生クラスに実装します。

2. 保護された変更されたクローン メソッド

java.lang.Object では、クローン メソッドを保護された変更されたクローン メソッドに設定します。これは非常に特殊な状況です。 protected のスコープは、パッケージの可視 + 継承可能です。この設定の理由は、このメソッドがクローン オブジェクトを返すためです。つまり、クローン メソッドの型を決定する方法がないため、当然のことながら、将来の世代はそれを実装して書き換えることしかできません。 , オープンになりすぎずに子孫に継承できるようにするために、保護された型に設定されます。

3. clone メソッドを実装するには、Cloneable インターフェイスを実装する必要があります

では、clone メソッドを書き直すときに、なぜ Cloneable インターフェイスを実装する必要があるのでしょうか?実際、Cloneable インターフェースは、Java のマークされたインターフェースです。マークされたインターフェースは、メソッドや属性を持たないインターフェースを指します。これらは、全員に何らかの情報を知らせるためにのみ存在し、xxx インスタンスの Cloneable を使用するときに判断できます。 Cloneable インターフェイスの登場により、設計者はクローン作成が必要であることを知ることができます。オブジェクトを複製する必要があるが、Cloneable インターフェイスを実装していない場合 (実際には、ここでの「実装」は「書き込み」に置き換えた方が正確です)、チェック例外が生成されます。

4. 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() はコピーするオブジェクトを識別し、このオブジェクトに領域を割り当て、オブジェクトをコピーし、元のオブジェクトの内容を新しいオブジェクトの記憶領域に 1 つずつコピーします。この複製されたオブジェクトでは、すべての属性は複製されたオブジェクトの属性と同じであり、これらの同じ属性は 2 つのタイプに分けられます:

1 つ目: 8 つのプリミティブ タイプと不変オブジェクト (文字列など)
2 番目のタイプ: 他のクラスのオブジェクト

最初のタイプの場合、clone メソッドは問題なくその値を元のオブジェクトの値に設定します。 2 番目のタイプの場合、clone メソッドはコピーされた新しいオブジェクトの参照を、元のオブジェクトが指す参照を指すだけで、2 番目のタイプのクラス オブジェクトは 2 つのオブジェクトによって変更されます。そこで今回は、深いコピーと浅いコピーの概念が関係します。

(2) 浅いコピー

浅いコピー: コピーされたオブジェクトのすべての変数には元のオブジェクトと同じ値が含まれており、他のオブジェクトへのすべての参照は依然として元のオブジェクトを指します。つまり、浅いコピーは、参照しているオブジェクトではなく、問題のオブジェクトのみをコピーします。

比如举个例子,一个类A中有另外一个类B类型的变量。在A重写clone函数调用super.clone的时候,创建的新对象和原来对象中的类B类型的变量是同一个,他们指向了同一个B的类型变量。如果在A中对B的变量做了修改,在新的拷贝出来的对象中B的变量也会被同样的修改。

请记住,直接调用super.clone实现的clone方法全部都是浅拷贝。

(三)深拷贝

深拷贝:被拷贝对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。

通俗的说,如果说浅拷贝,开始的时候是两条线,如果在最后有一个其他类的变量,那么这两条线最后会合二为一,共同指向这变量,都能对他进行操作。深拷贝则是完完全全的两条线,互不干涉,因为他已经把所有的内部中的变量的对象全都复制一遍了。

深拷贝在代码中,需要在clone方法中多书写调用这个类中其他类的变量的clone函数。

(四)串行化深拷贝

在框架中,有的时候我们发现其中并没有重写clone方法,那么我们在需要拷贝一个对象的时候是如何去操作的呢?答案是我们经常会使用串行化方法,实现Serializable接口。

去寻找其他的方法来替代深拷贝也是无可奈何的事情,如果采用传统的深拷贝,难道你拷贝一个对象的时候向其中追无数层来拷贝完所有的对象变量么?先不谈这么做的时间消耗,仅仅是写这样的代码都会让人望而生畏。串行化深拷贝就是这样一个相对简单的方法。

把对象写到流里的过程是串行化(Serilization)过程,但是在Java程序师圈子里又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的并行化(Deserialization)过程则叫做 “解冻”或者“回鲜(depicking)”过程。应当指出的是,写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。

上面是网上的专业解释,我也不在这里班门弄斧了。在Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里(腌成咸菜),再从流里读出来(把咸菜回鲜),便可以重建对象。

public Object deepClone() 
{ 
 //写入对象 
 ByteArrayOutoutStream 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代码以字节序列的形式写出,即我们上面代码前三行写入对象),Java的这种序列化模式为开发者提供了很多便利,可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个的所有属性和方法都会自动序列化。但是有种情况是有些属性是不需要序列号的,所以就用到这个关键字。只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。

(五)总结

在实际的应用中,深拷贝和浅拷贝只是两个概念,不一定谁比谁好,要按照实际的工作来确定如何去拷贝一个对象。如果在数据库操作方面,为了取出一张表时不涉及其他的表,肯定需要使用浅拷贝,而在框架的Serializable中,虽然耗时,但是深拷贝是非常有必要的。

在java中,拷贝分为深拷贝和浅拷贝两种。java在公共超类Object中实现了一种叫做clone的方法,这种方法clone出来的新对象为浅拷贝,而通过自己定义的clone方法为深拷贝。

 以上就是java拷贝机制详解的内容,更多相关内容请关注PHP中文网(www.php.cn)!


関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!