Javaでオブジェクトをコピーするにはどうすればよいですか?


794

以下のコードを検討してください:

DummyBean dum = new DummyBean();
dum.setDummy("foo");
System.out.println(dum.getDummy()); // prints 'foo'

DummyBean dumtwo = dum;
System.out.println(dumtwo.getDummy()); // prints 'foo'

dum.setDummy("bar");
System.out.println(dumtwo.getDummy()); // prints 'bar' but it should print 'foo'

だから、に影響を与えずにdumto をコピーしてdumtwo変更したいと思います。しかし、上記のコードはそれを行っていません。で何かを変更すると、同じ変更が発生します。dumdumtwodumdumtwo

おそらく、dumtwo = dumJavaは参照のみをコピーします。それで、の新しいコピーを作成してdumそれに割り当てる方法はありますdumtwoか?

回答:


611

コピーコンストラクターを作成します。

class DummyBean {
  private String dummy;

  public DummyBean(DummyBean another) {
    this.dummy = another.dummy; // you can access  
  }
}

すべてのオブジェクトには、オブジェクトのコピーに使用できるcloneメソッドもありますが、使用しないでください。クラスを作成して不適切なcloneメソッドを実行するのは簡単すぎます。そうするつもりなら、少なくとも効果的なJavaでジョシュア・ブロックがそれについて言っていることを読んでください。


45
しかし、彼は自分のコードをDummyBean two = new DummyBean(one);に変更する必要があります。正しい?
Chris K

12
この方法は、ディープコピーと同じことを効果的に実行しますか?
マシューピジアック2011

124
@MatthewPiziak、私にとって-これはディープクローンではありません。ネストされたオブジェクトは元のソースインスタンスを参照しているため、各参照(非値型)オブジェクトが上記と同じコンストラクタテンプレートを提供しない限り、複製ではありません。
SliverNinja-MSFT

17
@Timmmm:はい、同じ文字列を参照しますが、それは不変であるため、問題ありません。プリミティブについても同様です。プリミティブ以外の場合は、コンストラクター呼び出しを再帰的にコピーするだけです。たとえば、DummyBeanがFooBarを参照した場合、FooBarにはコンストラクタFooBar(FooBar another)があり、dummyはthis.foobar = new FooBar(another.foobar)を呼び出す必要があります
egaga

7
@ChristianVielma:いいえ、「johndoe」にはなりません。ティムムが言ったように、文字列自体は不変です。1つのsetDummy(..)を使用すると、「johndoe」を指すように参照を1つに設定できますが、1つには設定しません。
keuleJ 2012年

404

基本: Javaでのオブジェクトのコピー。

私たちは、オブジェクト-と仮定しましょうobj1二つのオブジェクトが含まれ、containedObj1containedObj2を
ここに画像の説明を入力してください

浅いコピー:
浅いコピーinstanceは、同じクラスのnew を作成し、すべてのフィールドを新しいインスタンスにコピーして返します。オブジェクトクラスcloneメソッドを提供し、浅いコピーのサポートを提供します。
ここに画像の説明を入力してください

ディープコピー:ディープコピーは、オブジェクトが参照するオブジェクトとともにオブジェクトがコピーさ
れるときに発生します。下の画像はobj1、ディープコピーが実行された後の状態を示しています。がコピーされただけobj1でなく、その中に含まれているオブジェクトもコピーされました。Java Object Serializationディープコピーの作成に使用できます。残念ながら、このアプローチにもいくつかの問題があります(詳細な例)。
ここに画像の説明を入力してください

考えられる問題:
clone正しく実装するには注意が必要です。防御的コピーコピーコンストラクター(@egaga応答として)、または静的なファクトリーメソッド
を使用することをお勧めします

  1. オブジェクトがあり、パブリックclone()メソッドがあることはわかっているが、コンパイル時にオブジェクトのタイプがわからない場合は、問題があります。Javaにはと呼ばれるインターフェースがありますCloneable。実際には、オブジェクトを作成する場合は、このインターフェースを実装する必要がありますCloneableObject.clone保護されているため、アクセス可能にするには、パブリックメソッドでオーバーライドする必要があります
  2. 私たちがしようとすると、別の問題が発生する深いコピー複雑なオブジェクトを。仮定しclone()、すべてのメンバーオブジェクト変数の方法も深いコピーをして、これは仮定の危険すぎるです。すべてのクラスでコードを制御する必要があります。

たとえば、org.apache.commons.lang.SerializationUtilsには、serialization(Source)を使用したディープクローンのメソッドがあります。Beanのクローンを作成する必要がある場合、org.apache.commons.beanutilsSource)にいくつかのユーティリティメソッドがあります。

  • cloneBean Beanクラス自体がCloneableを実装していない場合でも、利用可能なプロパティのゲッターとセッターに基づいてBeanを複製します。
  • copyProperties プロパティ名が同じであるすべての場合に、プロパティ値を起点Beanから宛先Beanにコピーします。

1
別のオブジェクトに含まれるオブジェクトについて説明していただけますか?
Freakyuser 2013年

1
@Chandra Sekhar "浅いコピーは同じクラスの新しいインスタンスを作成し、すべてのフィールドを新しいインスタンスにコピーしてそれを返します"すべてのフィールドについて言及するのは間違っています。bczオブジェクトはコピーされず、参照のみがコピーされます。古いオブジェクト(元のオブジェクト)が指していたのと同じオブジェクト。
JAVA

4
@sunny-Chandraの説明が正しい。そして何が起こるかについてのあなたの説明もそうです。「すべてのフィールドをコピーする」の意味を正しく理解していないと私は言っています。フィールド参照であり、参照されているオブジェクトではありません。「すべてのフィールドをコピーする」とは、「すべての参照をコピーする」ことを意味します。「すべてのフィールドをコピーする」という文について、あなたと同じように誤解している人にとって、これが正確に何を意味するかを指摘したのは良いことです。:)
ToolmakerSteve 2014

2
...オブジェクトへの「ポインタ」を使用して下位オブジェクト指向言語の観点から考えると、そのようなフィールドには、オブジェクトデータが見つかったメモリ内のアドレス(「0x70FF1234」など)が含まれます。そのアドレスは、コピー(割り当て)される「フィールド値」です。最終的には、両方のオブジェクトに同じオブジェクトを参照する(指す)フィールドがあることは正しいです。
ToolmakerSteve 2014

127

パッケージimport org.apache.commons.lang.SerializationUtils;にはメソッドがあります:

SerializationUtils.clone(Object);

例:

this.myObjectCloned = SerializationUtils.clone(this.object);

59
オブジェクトが実装する限りSerializable
Androiderson

2
この場合、最後のオブジェクトが静的である場合、クローンオブジェクトは元のオブジェクトを参照しません。
Dante、

8
オブジェクトを複製するだけのサードパーティライブラリ!
カーン

2
@カーン、「まさにサードパーティのライブラリ」は完全に別の議論です!:D
チャールズウッド

103

以下のようにしてください:

public class Deletable implements Cloneable{

    private String str;
    public Deletable(){
    }
    public void setStr(String str){
        this.str = str;
    }
    public void display(){
        System.out.println("The String is "+str);
    }
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

そして、あなたが別のオブジェクトを取得したいところはどこでも、クローンを実行するだけです。例えば:

Deletable del = new Deletable();
Deletable delTemp = (Deletable ) del.clone(); // this line will return you an independent
                                 // object, the changes made to this object will
                                 // not be reflected to other object

1
これをテストしましたか?私はこれを自分のプロジェクトに使用することができ、正しいことが重要です。
霧の深い

2
@misty私はそれをテストしました。私のプロダクションアプリで完全に動作します
Andrii Kovalchuk '09 / 09/27

複製後、元のオブジェクトを変更すると、複製も変更されます。
シビッシュ2017

4
これは、要求されたディープコピーではないという点で間違っています。
Bluehorn 2017年

1
このメソッドは、複製可能なオブジェクトを指すポインターを複製しますが、両方のオブジェクト内のすべてのプロパティは同じです。したがって、メモリ内に新しいオブジェクトが作成されますが、各オブジェクト内のデータはメモリからの同じデータです
Omar HossamEldin

40

なぜReflection APIを使用しても答えがないのですか?

private static Object cloneObject(Object obj){
        try{
            Object clone = obj.getClass().newInstance();
            for (Field field : obj.getClass().getDeclaredFields()) {
                field.setAccessible(true);
                field.set(clone, field.get(obj));
            }
            return clone;
        }catch(Exception e){
            return null;
        }
    }

とても簡単です。

編集:再帰を介して子オブジェクトを含める

private static Object cloneObject(Object obj){
        try{
            Object clone = obj.getClass().newInstance();
            for (Field field : obj.getClass().getDeclaredFields()) {
                field.setAccessible(true);
                if(field.get(obj) == null || Modifier.isFinal(field.getModifiers())){
                    continue;
                }
                if(field.getType().isPrimitive() || field.getType().equals(String.class)
                        || field.getType().getSuperclass().equals(Number.class)
                        || field.getType().equals(Boolean.class)){
                    field.set(clone, field.get(obj));
                }else{
                    Object childObj = field.get(obj);
                    if(childObj == obj){
                        field.set(clone, clone);
                    }else{
                        field.set(clone, cloneObject(field.get(obj)));
                    }
                }
            }
            return clone;
        }catch(Exception e){
            return null;
        }
    }

これははるかに良く見えますが、setAccessible(true)が失敗する可能性があるため、最終フィールドのみを考慮する必要があるため、field.set(clone、field.get(obj))を個別に呼び出すときにスローされる例外IllegalAccessExceptionを個別に処理する必要がある場合があります。
最大

1
私はそれがとても好きですが、ジェネリックスを使用するようにリファクタリングできますか?プライベート静的<T> T cloneObject(T obj){....}
Adelin

2
我々はそれの両親へのプロパティからの参照を持っているとき、私はそれの問題を考える: Class A { B child; } Class B{ A parent; }
nhthai

この状況でも失敗し、処理する必要があります。私はそれを明日遊びます。 class car { car car = new car(); }
JANЯabčan

2
これはエラーが発生しやすくなります。コレクションの処理方法がわからない
ACV 2017年

31

GoogleのJSONライブラリを使用してシリアル化し、シリアル化されたオブジェクトの新しいインスタンスを作成します。いくつかの制限付きでディープコピーを行います:

  • 再帰的な参照はありません

  • 異なるタイプの配列はコピーしません

  • 配列とリストを入力する必要があります。そうしないとインスタンス化するクラスが見つかりません

  • 自分で宣言したクラスに文字列をカプセル化する必要があるかもしれません

また、このクラスを使用して、ユーザー設定、ウィンドウ、および実行時に再ロードされないものを保存します。とても使いやすく効果的です。

import com.google.gson.*;

public class SerialUtils {

//___________________________________________________________________________________

public static String serializeObject(Object o) {
    Gson gson = new Gson();
    String serializedObject = gson.toJson(o);
    return serializedObject;
}
//___________________________________________________________________________________

public static Object unserializeObject(String s, Object o){
    Gson gson = new Gson();
    Object object = gson.fromJson(s, o.getClass());
    return object;
}
       //___________________________________________________________________________________
public static Object cloneObject(Object o){
    String s = serializeObject(o);
    Object object = unserializeObject(s,o);
    return object;
}
}

これはうまくいきます。ただし、List <Integer>などのクローンを作成する場合は注意してください。バギーになります、私の整数はダブルス、100.0になりました。彼らがなぜそういうのか理解するのに長い時間がかかりました。解決策は、整数を1つずつ複製し、サイクルでリストに追加することでした。
paakjis

24

はい、オブジェクトへの参照を作成しているだけです。が実装されてCloneableいる場合は、オブジェクトのクローンを作成できます。

オブジェクトのコピーに関するこのWiki記事を確認してください。

ここを参照:オブジェクトのコピー


14

Cloneable以下のコードをクラスに追加します

public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

これを使って clonedObject = (YourClass) yourClassObject.clone();



12

これも機能します。想定モデル

class UserAccount{
   public int id;
   public String name;
}

まずcompile 'com.google.code.gson:gson:2.8.1'、アプリに追加 します> gradle&sync。その後

Gson gson = new Gson();
updateUser = gson.fromJson(gson.toJson(mUser),UserAccount.class);

transientアクセス修飾子の後にキーワードを使用して、フィールドの使用を除外できます。

注:これは悪い習慣です。また、使用をお勧めしませんCloneableまたはJavaSerializationそれは遅くて壊れています。最高のパフォーマンスのために書くコピーコンストラクタ参照

何かのようなもの

class UserAccount{
        public int id;
        public String name;
        //empty constructor
        public UserAccount(){}
        //parameterize constructor
        public UserAccount(int id, String name) {
            this.id = id;
            this.name = name;
        }

        //copy constructor
        public UserAccount(UserAccount in){
            this(in.id,in.name);
        }
    }

90000反復の統計のテスト:
ラインUserAccount clone = gson.fromJson(gson.toJson(aO), UserAccount.class);808msかかります

ラインUserAccount clone = new UserAccount(aO);1ms未満かかります

結論:上司が頭がおかしく、スピードを好む場合は、gsonを使用します。品質を優先する場合は、2番目のコピーコンストラクターを使用します。

Android Studioでコピーコンストラクターコードジェネレータープラグインを使用することもできます。


それが悪い習慣であるのに、なぜそれを提案したのですか?
Parth Mehrotra 2017

ありがとう@ParthMehrotraが改善されました
Qamar


9

ディープクローニングユーティリティを使用します。

SomeObjectType copy = new Cloner().deepClone(someObject);

これにより、Javaオブジェクトがディープコピーされます。https://github.com/kostaskougios/cloningで確認してください。


1
カスタムクラスを使用して私のために動作しませんでした。次の例外が発生します:java.lang.NoClassDefFoundError:sun.reflect.ReflectionFactory
stefanjunker

9

ディープクローニングが答えです。Cloneableインターフェースを実装し、メソッドをオーバーライドする必要がありますclone()

public class DummyBean implements Cloneable {

   private String dummy;

   public void setDummy(String dummy) {
      this.dummy = dummy;
   }

   public String getDummy() {
      return dummy;
   }

   @Override
   public Object clone() throws CloneNotSupportedException {
      DummyBean cloned = (DummyBean)super.clone();
      cloned.setDummy(cloned.getDummy());
      // the above is applicable in case of primitive member types like String 
      // however, in case of non primitive types
      // cloned.setNonPrimitiveType(cloned.getNonPrimitiveType().clone());
      return cloned;
   }
}

あなたはこのようにそれを呼ぶでしょう DummyBean dumtwo = dum.clone();


2
dummy、a String、は不変であり、コピーする必要はありません
Steve Kuo

7

そのためには、何らかの方法でオブジェクトを複製する必要があります。Javaには複製メカニズムがありますが、必要がない場合は使用しないでください。コピーを実行するコピーメソッドを作成してから、次の操作を行います。

dumtwo = dum.copy();

ここでは、コピーを実現するためのさまざまな手法に関するいくつかのアドバイスを示します。


6

明示的にコピーする以外の別のアプローチは、オブジェクトを不変にすることです(no setまたは他のミューテーターメソッド)。このようにして問題が生じることはありません。不変性は、オブジェクトが大きくなるほど難しくなりますが、その反対に、コヒーレントな小さなオブジェクトとコンポジットに分割する方向に押し出されます。


5

egagaのコピーコンストラクターメソッド代替。おそらくすでにPOJOを持っているのでcopy()、初期化されたオブジェクトのコピーを返す別のメソッドを追加するだけです。

class DummyBean {
    private String dummyStr;
    private int dummyInt;

    public DummyBean(String dummyStr, int dummyInt) {
        this.dummyStr = dummyStr;
        this.dummyInt = dummyInt;
    }

    public DummyBean copy() {
        return new DummyBean(dummyStr, dummyInt);
    }

    //... Getters & Setters
}

すでにがDummyBeanあり、コピーが必要な場合:

DummyBean bean1 = new DummyBean("peet", 2);
DummyBean bean2 = bean1.copy(); // <-- Create copy of bean1 

System.out.println("bean1: " + bean1.getDummyStr() + " " + bean1.getDummyInt());
System.out.println("bean2: " + bean2.getDummyStr() + " " + bean2.getDummyInt());

//Change bean1
bean1.setDummyStr("koos");
bean1.setDummyInt(88);

System.out.println("bean1: " + bean1.getDummyStr() + " " + bean1.getDummyInt());
System.out.println("bean2: " + bean2.getDummyStr() + " " + bean2.getDummyInt());

出力:

bean1: peet 2
bean2: peet 2

bean1: koos 88
bean2: peet 2

しかし、どちらもうまく機能し、最終的にはあなた次第です...



3

XStreamを使用して、http://x-stream.github.io/から自動的にディープコピーできます。

XStreamは、オブジェクトをXMLにシリアル化して再び戻す単純なライブラリです。

プロジェクトに追加します(Mavenを使用している場合)

<dependency>
    <groupId>com.thoughtworks.xstream</groupId>
    <artifactId>xstream</artifactId>
    <version>1.3.1</version>                
</dependency>

その後

DummyBean dum = new DummyBean();
dum.setDummy("foo");
DummyBean dumCopy = (DummyBean) XSTREAM.fromXML(XSTREAM.toXML(dum));

これにより、複製インターフェースを実装する必要のないコピーが作成されます。


29
XMLへの変換やXMLからの変換は、あまり洗練されていないようです。軽く入れて!
Timmmm 2012年

java.beans.XMLEncoderXMLにシリアル化する標準のJava APIも調べてください(詳細なコピーの目的ではありません)。
Jaime Hablutzel、2015

1
これがどれほど重いか知っていますか?
mahieddine

1
サードパーティのライブラリを追加してオブジェクトのシリアル化を行う必要があるため、オーバーヘッドが大きくなる可能性があります。これは、パフォーマンスに大きな影響を与える可能性があります。
NiThDi 2016年

2
public class MyClass implements Cloneable {

private boolean myField= false;
// and other fields or objects

public MyClass (){}

@Override
public MyClass clone() throws CloneNotSupportedException {
   try
   {
       MyClass clonedMyClass = (MyClass)super.clone();
       // if you have custom object, then you need create a new one in here
       return clonedMyClass ;
   } catch (CloneNotSupportedException e) {
       e.printStackTrace();
       return new MyClass();
   }

  }
}

あなたのコードで:

MyClass myClass = new MyClass();
// do some work with this object
MyClass clonedMyClass = myClass.clone();

2
例外をキャッチしようとしてスローされない場合、宣言のset "throws CloneNotSupportedException"には意味がありません。だから、あなたはそれを取り除くことができます。
クリスチャン、

2

コピーするオブジェクトを渡して、必要なオブジェクトを取得します。

private Object copyObject(Object objSource) {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(objSource);
            oos.flush();
            oos.close();
            bos.close();
            byte[] byteData = bos.toByteArray();
            ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
            try {
                objDest = new ObjectInputStream(bais).readObject();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return objDest;

    }

次に、objDest目的のオブジェクトを解析します。

ハッピーコーディング!


1

メソッドを実装Cloneableして使用することができclone()ます。ただし、cloneメソッドを使用する場合は、標準で- Objectpublic Object clone()メソッドを常にオーバーライドする必要があります。


1

ソースファイルに注釈を追加できる場合は、このような注釈プロセッサまたはコードジェネレータを使用できます。

import net.zerobuilder.BeanBuilder

@BeanBuilder
public class DummyBean { 
  // bean stuff
}

手動で行うのと同じ方法で、浅いコピーを作成DummyBeanBuildersする静的メソッドdummyBeanUpdaterを持つクラスが生成されます。

DummyBean bean = new DummyBean();
// Call some setters ...
// Now make a copy
DummyBean copy = DummyBeanBuilders.dummyBeanUpdater(bean).done();

0

gsonオブジェクトの複製に使用します。

public static <T>T copyObject(Object object){
    Gson gson = new Gson();
    JsonObject jsonObject = gson.toJsonTree(object).getAsJsonObject();
    return gson.fromJson(jsonObject,(Type) object.getClass());
}

オブジェクトがあると仮定しますperson

Person copyPerson = copyObject(person);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.