Javaのコピー(ディープコピー、シャローコピー)

オブジェクトのコピーでは、参照をコピーしているのか、オブジェクトをコピーしているのかが初心者には分からない人も多いかもしれません。コピーには、参照コピー、浅いコピー、深いコピーに分けられます。

参照によるコピー

参照コピーは新しいオブジェクト参照アドレスを生成しますが、最後の 2 つのポイントは依然として同じオブジェクトを指します。リファレンスコピーをよりよく理解するにはどうすればよいでしょうか? それはとても簡単です。私たちを例にとると、私たちは通常、名前を持っていますが、状況や人々によって呼び方が異なるかもしれませんが、どの名前が「私」に属するかは非常に明確です。

 皆さんもコード例を見てみましょう (わかりやすくするために、get や set などのメソッドは記述されていません)。

class Son {

    String name;
    int age;

    public Son(String name, int age) {

        this.name = name;
        this.age = age;
    }
}

public class test {
    
    public static void main(String[] args) {

        Son s1 = new Son("son1", 12);
        Son s2 = s1;
        s1.age = 22;
        System.out.println(s1);
        System.out.println(s2);
        System.out.println("s1的age:" + s1.age);
        System.out.println("s2的age:" + s2.age);
        System.out.println("s1==s2" + (s1 == s2));//相等
    }
}

試験結果:

Son@135fbaa4
Son@135fbaa4
s1的age:22
s2的age:22
true

浅いコピー

参照を直接コピーするのではなく、オブジェクトを作成してターゲット オブジェクトのコンテンツをコピーするにはどうすればよいですか?

まず浅いコピーについて話しましょう。浅いコピーは新しいオブジェクトを作成します。新しいオブジェクトは元のオブジェクト自体とは何の関係もありません。新しいオブジェクトは元のオブジェクトと等しくありませんが、新しいオブジェクトのプロパティは古いオブジェクトと同じです具体的には、次のような違いが見られます。

  • 属性が基本型 (int、double、long、boolean など) の場合は、基本型の値がコピーされます。

  • 属性が参照型の場合、コピーされるのはメモリ アドレスです (つまり、参照はコピーされますが、参照されるオブジェクトはコピーされません)。そのため、オブジェクトの 1 つがこのアドレスを変更すると、他のオブジェクトにも影響します。

画像を使用してシャロー コピーを説明すると、次のようになります。

浅いコピーを実装するにはどうすればよいですか? これも非常に簡単です。つまり、コピーする必要があるクラスに Cloneable インターフェイスを実装し、その clone() メソッドを書き直すだけです

@Override
protected Object clone() throws CloneNotSupportedException {

    return super.clone();
}

 使用する場合は、クラスの clone() メソッドを直接呼び出すだけです。具体的なケースとしては以下のようなものがあります。

class Father{

    String name;
    public Father(String name) {

        this.name=name;
    }

    @Override
    public String toString() {

        return "Father{" +
                "name='" + name + '\'' +
                '}';
    }
}

class Son implements Cloneable {
 
    int age;
    String name;
    Father father;

    public Son(String name,int age) {

        this.age=age;
        this.name = name;
    }

    public Son(String name,int age, Father father) {

        this.age=age;
        this.name = name;
        this.father = father;
    }

    @Override
    public String toString() {

        return "Son{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", father=" + father +
                '}';
    }

    @Override
    protected Son clone() throws CloneNotSupportedException {
        // 字段father指向同一个引用。
        return (Son) super.clone();
    }
}

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

        Father f=new Father("bigFather");
        Son s1 = new Son("son1",13);
        s1.father=f;
        Son s2 = s1.clone();
        
        System.out.println(s1);
        System.out.println(s2);
        System.out.println("s1==s2:"+(s1 == s2));//不相等
        System.out.println("s1.name==s2.name:"+(s1.name == s2.name));//相等
        System.out.println();

        //但是他们的Father father 和String name的引用一样
        s1.age=12;
        s1.father.name="smallFather";//s1.father引用未变
        s1.name="son222";//类似 s1.name=new String("son222") 引用发生变化
        System.out.println("s1.Father==s2.Father:"+(s1.father == s2.father));//相等
        System.out.println("s1.name==s2.name:"+(s1.name == s2.name));//不相等
        System.out.println(s1);
        System.out.println(s2);
    }
}

操作結果:

Son{age=13, name='son1', father=Father{name='bigFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}
s1==s2:false
s1.name==s2.name:true//此时相等

s1.Father==s2.Father:true
s1.name==s2.name:false//修改引用后不等
Son{age=12, name='son222', father=Father{name='smallFather'}}
Son{age=13, name='son1', father=Father{name='smallFather'}}

当然のことですが、この種の浅いコピーは、双子や二人のように、オブジェクト自体を除いてコピーされたオブジェクトと同じ部分と関係を持ちますが、最初の外観とさまざまな関係(両親や親戚)は同じです。最初の浅いコピーは同じ文字列を指し、その後参照ポイントを変更するため、名前は最初は同じであることに注意してください。==s1.name="son222"

ディープコピー

上記の問題では、コピーされた 2 つのオブジェクトは異なりますが、一部の内部参照は依然として同じです。このオブジェクトを元のオブジェクトから完全に独立させるために、このオブジェクトを完全にコピーするにはどうすればよいでしょうか? ディープコピーを使用してください。ディープ コピー:参照データ型をコピーする場合、新しいオブジェクトが作成され、そのメンバー変数がコピーされます。

ディープコピーの具体的な実装では、clone()メソッドとsequenceメソッドを書き換える2つの方法を紹介します。

clone() メソッドをオーバーライドする

clone () メソッドがディープ コピーを実装するためにオーバーライドされる場合、クラス内のカスタム参照変数のすべてのクラスも clone () メソッドを実装するために Cloneable インターフェイスを実装する必要があります。文字クラスの場合、コピー用に新しい文字列を作成できます。

上記のコードの場合、Father クラスは Cloneable インターフェイスを実装し、 clone () メソッドをオーバーライドします。Son の clone() メソッドはすべての参照をコピーする必要があります

//Father clone()方法
@Override
protected Father clone() throws CloneNotSupportedException {
   
    return (Father) super.clone();
}

//Son clone()方法
@Override
protected Son clone() throws CloneNotSupportedException {
   
    Son son= (Son) super.clone();//待返回克隆的对象
    son.name=new String(name);
    son.father=father.clone();
    return son;
}

 他のコードは変更されず、実行結果は次のようになります。

Son{age=13, name='son1', father=Father{name='bigFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}
s1==s2:false
s1.name==s2.name:false

s1.Father==s2.Father:false
s1.name==s2.name:false
Son{age=12, name='son222', father=Father{name='smallFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}

連載

この方法によりディープコピーが実現されていることがわかります。しかし、この状況には問題があります。参照またはレイヤーが多すぎる場合はどうなるでしょうか。

オブジェクトごとにclone()をいちいち書くのは無理ですよね?それではどうすればいいでしょうか?連載付き。

シリアル化後、バイナリ バイト ストリーム コンテンツをメディア (テキストまたはバイト配列) に書き込み、このメディアからデータを読み取ると、元のオブジェクトがこのメディアに書き込まれ、クローン オブジェクトにコピーされます。クローン オブジェクトはこのメディアから読み取られるため、元のオブジェクトの変更はクローン オブジェクトには影響しません。

オブジェクト キャッシュに詳しい人は、Java オブジェクトを Redis にキャッシュし、シリアル化と逆シリアル化を使用して Redis から Java オブジェクトを読み取って生成することが多いことを知っています。一般に、Java オブジェクトはバイト ストリームまたは JSON 文字列として保存し、Java オブジェクトに逆シリアル化できます。シリアル化ではオブジェクトのプロパティは保存されますが、オブジェクトのアドレスに関する情報はメモリ内に保存されず、保存できないためですしたがって、すべての参照オブジェクトは、Java オブジェクトに逆シリアル化されるときに再作成されます。

具体的な実装に関しては、カスタム クラスはSerializable インターフェイスを実装する必要がありますこのクラスのオブジェクトを返すためにディープ コピーが必要なクラス (Son) に関数を定義します。

protected Son deepClone() throws IOException, ClassNotFoundException {
   
      Son son=null;
      //在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组中
      //默认创建一个大小为32的缓冲区
      ByteArrayOutputStream byOut=new ByteArrayOutputStream();
      //对象的序列化输出
      ObjectOutputStream outputStream=new ObjectOutputStream(byOut);//通过字节数组的方式进行传输
      outputStream.writeObject(this);  //将当前student对象写入字节数组中

      //在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区
      ByteArrayInputStream byIn=new ByteArrayInputStream(byOut.toByteArray()); //接收字节数组作为参数进行创建
      ObjectInputStream inputStream=new ObjectInputStream(byIn);
      son=(Son) inputStream.readObject(); //从字节数组中读取
      return  son;
}

使用する場合は、作成したメソッドを呼び出すだけで、他のメソッドは変更されません。得られる効果は次のとおりです。

Son{age=13, name='son1', father=Father{name='bigFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}
s1==s2:false
s1.name==s2.name:false

s1.Father==s2.Father:false
s1.name==s2.name:false
Son{age=12, name='son222', father=Father{name='smallFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}

注: 2023/07/01 は、clone メソッドをオーバーライドする例です。

package learnjavafx8;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;

/**
 * @copyright 2023-2022
 * @package   learnjavafx8
 * @file      Person.java
 * @date      2023-07-01 14:34
 * @author    qiao wei
 * @version   1.0
 * @brief     
 * @history
 */
public class Person implements Cloneable {

	public static void main(String[] args) {
		Person p01 = new Person(10, 20, true);
		System.out.println(p01);
		
		Person p02 = (Person) p01.clone();
		p02.firstIntegerProperty.set(-233);
		p02.secondIntegerProperty.set(666);
		p02.flag = false;

		System.out.println(p01);
		System.out.println(p02);
	}
	
	public Person() {
		firstIntegerProperty = new SimpleIntegerProperty();
		secondIntegerProperty = new SimpleIntegerProperty();
		
		flag = false;
	}
	
	public Person(int first, int second, boolean flag) {
		firstIntegerProperty = new SimpleIntegerProperty(first);
		secondIntegerProperty = new SimpleIntegerProperty(second);
		
		this.flag = flag;
	}

	@Override
	protected Object clone() {
		Person person = null;
		
		try {
			person = (Person) super.clone();
			
			person.firstIntegerProperty =
				new SimpleIntegerProperty(this.firstIntegerProperty.get());
			person.secondIntegerProperty =
				new SimpleIntegerProperty(this.secondIntegerProperty.get());
			person.flag = this.flag;
		} catch (CloneNotSupportedException exception) {
			exception.printStackTrace();
		} finally {
			return person;
		}
	}

	@Override
	public String toString() {
		return "Person{" +
			"firstIntegerProperty=" + firstIntegerProperty +
			", secondIntegerProperty=" + secondIntegerProperty +
			", flag=" + flag +
			'}';
	}

	public IntegerProperty firstIntegerProperty = null;
	
	public IntegerProperty secondIntegerProperty = null;
	
	public boolean flag = false;
}

テストの結果、p02 参照フィールドの変更は p01 参照フィールドに影響を与えません。ディープコピーが作成されました。

Person{firstIntegerProperty=IntegerProperty [value: 10], secondIntegerProperty=IntegerProperty [value: 20], flag=true}
Person{firstIntegerProperty=IntegerProperty [value: 10], secondIntegerProperty=IntegerProperty [value: 20], flag=true}
Person{firstIntegerProperty=IntegerProperty [value: -233], secondIntegerProperty=IntegerProperty [value: 666], flag=false}

Process finished with exit code 0

Person クラスの clone メソッドを変更し、深いコピーを浅いコピーに変更します (参照型データ コピーの内容を削除します)。コードの残りの部分は変更されません。

	@Override
	protected Object clone() {
		Person person = null;
		
		try {
			person = (Person) super.clone();
			
//			person.firstIntegerProperty =
//				new SimpleIntegerProperty(this.firstIntegerProperty.get());
//			person.secondIntegerProperty =
//				new SimpleIntegerProperty(this.secondIntegerProperty.get());
//			person.flag = this.flag;
		} catch (CloneNotSupportedException exception) {
			exception.printStackTrace();
		} finally {
			return person;
		}
	}

操作結果:

Person{firstIntegerProperty=IntegerProperty [value: 10], secondIntegerProperty=IntegerProperty [value: 20], flag=true}
Person{firstIntegerProperty=IntegerProperty [value: -233], secondIntegerProperty=IntegerProperty [value: 666], flag=true}
Person{firstIntegerProperty=IntegerProperty [value: -233], secondIntegerProperty=IntegerProperty [value: 666], flag=false}

Process finished with exit code 0

p02 の参照フィールドの変更は、p01 の参照フィールドの内容に影響します。

おすすめ

転載: blog.csdn.net/weiweiqiao/article/details/131490567