Java–シーケンス化によるオブジェクトのコピー

6919 ワード

JavaにインタフェースCloneableが存在することを知っています.このインタフェースを実現するクラスはコピーされる能力を備えています.同時にコピーはメモリの中で行われ、性能の面ではnewによって直接オブジェクトを生成するよりも速く、特に大きなオブジェクトの生成では性能の向上が明らかになります.しかし、コピーは深いコピーと浅いコピーに分かれていることは知っていますが、浅いコピーにはオブジェクト属性のコピーが不十分な問題があります.
浅いコピーの問題
まず、次のコードを見てみましょう.
public class Person implements Cloneable{
    /**    **/
    private String name;
    
    /**      **/
    private Email email;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Email getEmail() {
        return email;
    }

    public void setEmail(Email email) {
        this.email = email;
    }
    
    public Person(String name,Email email){
        this.name  = name;
        this.email = email;
    }
    
    public Person(String name){
        this.name = name;
    }

    protected Person clone() {
        Person person = null;
        try {
            person = (Person) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        
        return person;
    }
}

public class Client {
    public static void main(String[] args) {
        //    
        Email email = new Email("     ","    12:30         ...");
        
        Person person1 =  new Person("  ",email);
        
        Person person2 =  person1.clone();
        person2.setName("  ");
        Person person3 =  person1.clone();
        person3.setName("  ");
        
        System.out.println(person1.getName() + "      :" + person1.getEmail().getContent());
        System.out.println(person2.getName() + "      :" + person2.getEmail().getContent());
        System.out.println(person3.getName() + "      :" + person3.getEmail().getContent());
    }
}

Output:
        :    12:30         ...
        :    12:30         ...
        :    12:30         ...

このアプリケーションでは、まず1通のメールを定義し、張三、李四、王五の3人に送信します.彼らは同じメールを使用しており、名前が異なるだけなので、張三のオブジェクトクラスを使用して李四、王五のオブジェクトをコピーし、名前を変更すればいいです.プログラムはここまで間違いありませんが、3枚前に30分前に着く必要がある場合は、メールの内容を修正します.
public class Client {
    public static void main(String[] args) {
        //    
        Email email = new Email("     ","    12:30         ...");
        
        Person person1 =  new Person("  ",email);
        
        Person person2 =  person1.clone();
        person2.setName("  ");
        Person person3 =  person1.clone();
        person3.setName("  ");
        
        person1.getEmail().setContent("    12:00         ...");
        
        System.out.println(person1.getName() + "      :" + person1.getEmail().getContent());
        System.out.println(person2.getName() + "      :" + person2.getEmail().getContent());
        System.out.println(person3.getName() + "      :" + person3.getEmail().getContent());
    }
}

ここでは同様に張三を用いて李四、王五のコピーを実現し、最後に張三のメール内容を今日12:00から二会議室に会議に参加してください....しかし、結果は次のとおりです.
张三のメールの内容は:今日12:00に二会议室に行って会议に参加してください...李四のメールの内容は:今日12:00に二会議室に行って会議に参加してください...王五のメールの内容は:今日12:00に二会議室に行って会議に参加してください...ここで私たちはなぜ李四と王五のメールの内容も変わったのか疑問に思っています.彼らを30分前に人に行かせたら文句があるよ.
実際に問題が発生する鍵はclone()メソッドにあり、このclone()メソッドはObjectクラスのclone()メソッドを使用することを知っていますが、このメソッドにはオブジェクトのすべての属性をすべてコピーするのではなく、選択的なコピーがあるという欠陥があります.基本的なルールは次のとおりです.
  • 基本タイプで、変数が基本的な非常タイプである場合、int、floatなどの値をコピーします.
  • オブジェクト.変数がインスタンスオブジェクトである場合、そのアドレス参照がコピーされます.つまり、新しいオブジェクトが元のオブジェクトと共通に使用されるインスタンス変数です.
  • String文字列、変数がString文字列の場合、そのアドレス参照がコピーされます.ただし、変更すると、文字列プールから新しい文字列が再生成され、元の文字列オブジェクトは変更されません.

  • 上のルールに基づいて、私たちは問題の所在を発見しやすくて、彼らの3つは1つのオブジェクトを公用して、張三はこのメールの内容を修正して、李四と王五も修正して、だからやっと上の情況が現れます.この場合、clone()メソッドにオブジェクトを新規作成し、張三がそのオブジェクトを参照するだけで解決できます.
    protected Person clone() {
            Person person = null;
            try {
                person = (Person) super.clone();
                person.setEmail(new Email(person.getEmail().getObject(),person.getEmail().getContent()));
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            
            return person;
        }
    

    したがって、浅いコピーはJavaが提供する簡単なコピーメカニズムにすぎず、直接使用するのは不便です.
    上記のソリューションでは、コピーによって生成されるオブジェクトが大量に存在する場合、クラスごとにclone()メソッドを書き、深いコピーを行い、大量のオブジェクトを新規作成する必要がある場合、このエンジニアリングは非常に大きく、ここではシーケンス化を利用してオブジェクトのコピーを実現することができます.
    シーケンス化によるオブジェクトのコピー
    シーケンス化を使用してオブジェクトのコピーを完了するにはどうすればいいですか?メモリ内のバイトストリームによるコピーは比較的容易に実現できる.親オブジェクトを1つのバイトストリームに書き込み、バイトストリームから読み出すことで、新しいオブジェクトを作成でき、その新しいオブジェクトと親オブジェクトの間に参照共有の問題がなく、オブジェクトの深いコピーを実現できます.
    public class CloneUtils {
        @SuppressWarnings("unchecked")
        public static  T clone(T obj){
            T cloneObj = null;
            try {
                //     
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                ObjectOutputStream obs = new ObjectOutputStream(out);
                obs.writeObject(obj);
                obs.close();
                
                //    ,      ,     
                ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
                ObjectInputStream ois = new ObjectInputStream(ios);
                //        
                cloneObj = (T) ois.readObject();
                ois.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return cloneObj;
        }
    }
    

    このツールクラスを使用するオブジェクトは、Serializableインタフェースを実装する必要があります.そうしないと、クローンを実装できません.
    public class Person implements Serializable{
        private static final long serialVersionUID = 2631590509760908280L;
    
        ..................
        //  clone()  
    
    }
    
    public class Email implements Serializable{
        private static final long serialVersionUID = 1267293988171991494L;
        
        ....................
    }
    

    したがって、このツールクラスを使用するオブジェクトは、Serializableインタフェースを実装するだけでオブジェクトのクローンを実装することができ、Cloneableインタフェース実装clone()メソッドを継承する必要はありません.
    public class Client {
        public static void main(String[] args) {
            //    
            Email email = new Email("     ","    12:30         ...");
            
            Person person1 =  new Person("  ",email);
            
            Person person2 =  CloneUtils.clone(person1);
            person2.setName("  ");
            Person person3 =  CloneUtils.clone(person1);
            person3.setName("  ");
            person1.getEmail().setContent("    12:00         ...");
            
            System.out.println(person1.getName() + "      :" + person1.getEmail().getContent());
            System.out.println(person2.getName() + "      :" + person2.getEmail().getContent());
            System.out.println(person3.getName() + "      :" + person3.getEmail().getContent());
        }
    }
    
    Output:
            :    12:00         ...
            :    12:30         ...
            :    12:30         ...
    

    基础を固めて、技术を高めて、困难を恐れないで、ピークに登ります!!!