この記事では、Java オブジェクトのコピーの詳細な説明と例を主に紹介します。必要な方は参考にしてください。
Java オブジェクトのコピーの詳細な説明と例
Java の割り当ては、オブジェクト参照をコピーすることです。オブジェクトのコピーを取得し、代入操作を使用しても目的を達成できません:
@Test public void testassign(){ Person p1=new Person(); p1.setAge(31); p1.setName("Peter"); Person p2=p1; System.out.println(p1==p2);//true }
オブジェクトの新しいコピーを作成した場合、つまり、初期状態はまったく同じですが、将来的にはそれぞれの状態を変更することができます。相互に影響を与えるため、Java でネイティブ clone() メソッドなどのオブジェクトのコピーを使用する必要があります。
オブジェクトのクローンを作成する方法
Object オブジェクトには clone() メソッドがあり、これによりオブジェクト内の各属性のコピーが実現されますが、その可視範囲は保護されているため、エンティティ クラスのクローン作成を使用するための前提条件は次のとおりです。
①実装 マーカーインターフェースであるCloneableインターフェースには独自のメソッドがありません。
② clone() メソッドをオーバーライドし、パブリックへの可視性を高めます。
@Data public class Person implements Cloneable { private String name; private Integer age; private Address address; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } @Test public void testShallowCopy() throws Exception{ Person p1=new Person(); p1.setAge(31); p1.setName("Peter"); Person p2=(Person) p1.clone(); System.out.println(p1==p2);//false p2.setName("Jacky"); System.out.println("p1="+p1);//p1=Person [name=Peter, age=31] System.out.println("p2="+p2);//p2=Person [name=Jacky, age=31] }
テスト ケースには基本的なメンバー タイプが 2 つだけあり、テストの目的は達成されています。
物事はそれほど単純ではないようです。Address クラスのメンバーを person に追加します。
@Data public class Address { private String type; private String value; }
もう一度テストしてみましょう。問題が発生します。
@Test public void testShallowCopy() throws Exception{ Address address=new Address(); address.setType("Home"); address.setValue("北京"); Person p1=new Person(); p1.setAge(31); p1.setName("Peter"); p1.setAddress(address); Person p2=(Person) p1.clone(); System.out.println(p1==p2);//false p2.getAddress().setType("Office"); System.out.println("p1="+p1); System.out.println("p2="+p2); }
出力を表示します:
false p1=Person(name=Peter, age=31, address=Address(type=Office, value=北京)) p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))
p2 のアドレス タイプを変更しただけで、両方のアドレス タイプが Office になりました。
浅いコピーと深いコピー
前の例は、浅いコピーと深いコピーの典型的な使用例です。
浅いコピー: コピーされたオブジェクトのすべての値属性には、元のオブジェクトの値と同じ値が含まれており、すべてのオブジェクト参照属性は引き続き元のオブジェクトを指します。
深いコピー: 浅いコピーに基づいて、他のオブジェクトを参照するすべての変数も複製され、コピーされた新しいオブジェクトを指します。
言い換えれば、デフォルトの clone() メソッド実装メカニズムは依然として代入です。
コピーされた属性が基本型の場合、現在のクラスのクローン可能メカニズムを実装するだけで済みます。これは浅いコピーです。
コピーされたオブジェクトのプロパティに他のエンティティ クラス オブジェクトへの参照が含まれている場合、これらのエンティティ クラス オブジェクトはクローン可能なインターフェイスを実装し、 clone() メソッドをオーバーライドする必要があります。
@Data public class Address implements Cloneable { private String type; private String value; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
これでは十分ではありません。Person の clone() はその参照メンバーを明示的にクローンする必要があります。
@Data public class Person implements Cloneable { private String name; private Integer age; private Address address; @Override protected Object clone() throws CloneNotSupportedException { Object obj=super.clone(); Address a=((Person)obj).getAddress(); ((Person)obj).setAddress((Address) a.clone()); return obj; } }
前のテスト ケースを再実行します:
false p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京)) p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))
クローン メソッドのディープ コピーの概要
① カスタム オブジェクトのメンバーなど、非ネイティブ メンバーがある場合は、次のものが必要です:
このメンバーは Cloneable インターフェイスを実装し、 clone() メソッドをオーバーライドします。このメソッドを public に昇格させることを忘れないでください。
同時に、コピーしたクラスの clone() メソッドを変更し、メンバーの複製ロジックを追加します。
② コピーされたオブジェクトが Object を直接継承しない場合、各スーパークラスは間に他の継承レベルがあり、Cloneable インターフェイスを実装し、 clone() メソッドをオーバーライドする必要があります。
オブジェクトのメンバーとは異なり、継承関係における clone は、コピーされたクラスの clone() による冗長な作業を必要としません。
つまり、完全なディープ コピーを実装する場合、コピーされたオブジェクトの継承チェーンと参照チェーン内のすべてのオブジェクトはクローン メカニズムを実装する必要があります。
オブジェクトのメンバーが N 個あり、M レベルの継承関係がある場合、前の例は問題ありません。
シリアル化を使用してディープ コピーを実装します
たとえば、Cloneable を実装しても、継承チェーン上のオブジェクトの実装は強制されません。また、clone() をオーバーライドすることも必須ではありません。方法。したがって、コーディング プロセス中にリンクの 1 つを見落としやすくなり、複雑なプロジェクトのトラブルシューティングが困難になります。
信頼性の高いシンプルな方法を探している場合は、シリアル化が最適です。
1. コピーされたオブジェクトの継承チェーンおよび参照チェーン内のすべてのオブジェクトは、java.io.Serializable インターフェイスを実装します。これは比較的単純であり、メソッドを実装する必要はありません。serialVersionID 要件は必須ではないため、ディープ コピーには問題ありません。
2. 独自の deepClone メソッドを実装し、これをストリームに書き込み、読み出します。一般的に知られている:凍結融解。
@Data public class Person implements Serializable { private String name; private Integer age; private Address address; public Person deepClone() { Person p2=null; Person p1=this; PipedOutputStream out=new PipedOutputStream(); PipedInputStream in=new PipedInputStream(); try { in.connect(out); } catch (IOException e) { e.printStackTrace(); } try(ObjectOutputStream bo=new ObjectOutputStream(out); ObjectInputStream bi=new ObjectInputStream(in);) { bo.writeObject(p1); p2=(Person) bi.readObject(); } catch (Exception e) { e.printStackTrace(); } return p2; } }
プロトタイプ ファクトリ クラス
テストを容易にし、スペースを節約するために、ファクトリ クラスはカプセル化されています。
公平性を保つため、一部のツール ライブラリではキャッシュ メカニズムの使用を避け、プロトタイプ ファクトリを使用してください。
public class PersonFactory{ public static Person newPrototypeInstance(){ Address address = new Address(); address.setType("Home"); address.setValue("北京"); Person p1 = new Person(); p1.setAddress(address); p1.setAge(31); p1.setName("Peter"); return p1; } }
Dozerを使ってオブジェクトをコピーする
DozerはBean処理ライブラリです。
maven 依存関係
<dependency> <groupId>net.sf.dozer</groupId> <artifactId>dozer</artifactId> <version>5.5.1</version> </dependency>
テストケース:
@Data public class Person { private String name; private Integer age; private Address address; @Test public void testDozer() { Person p1=PersonFactory.newPrototypeInstance(); Mapper mapper = new DozerBeanMapper(); Person p2 = mapper.map(p1, Person.class); p2.getAddress().setType("Office"); System.out.println("p1=" + p1); System.out.println("p2=" + p2); } } @Data public class Address { private String type; private String value; }
出力:
p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京)) p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))
注: DozerBeanMapper オブジェクトが for ループ内で作成される場合、Dozer には深刻な問題があります (dozer:7358)。 10倍近く削減されます。 DozerBeanMapper はスレッドセーフであるため、毎回新しいインスタンスを作成する必要はありません。組み込みのシングルトン ファクトリ DozerBeanMapperSingletonWrapper を使用してマッパーを作成したり、それを Spring に統合したりできます。
さらに暴力的です。People クラスを作成します:
@Data public class People { private String name; private String age;//这里已经不是Integer了 private Address address; @Test public void testDozer() { Person p1=PersonFactory.newPrototypeInstance(); Mapper mapper = new DozerBeanMapper(); People p2 = mapper.map(p1, People.class); p2.getAddress().setType("Office"); System.out.println("p1=" + p1); System.out.println("p2=" + p2); } }
属性名が同じである限り、実行してください~
破壊を続けます:
@Data public class People { private String name; private String age; private Map<String,String> address;//�� @Test public void testDozer() { Person p1=PersonFactory.newPrototypeInstance(); Mapper mapper = new DozerBeanMapper(); People p2 = mapper.map(p1, People.class); p2.getAddress().put("type", "Office"); System.out.println("p1=" + p1); System.out.println("p2=" + p2); } }
Commons-BeanUtils を使用してオブジェクトをコピーします
maven の依存関係:
<dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.3</version> </dependency>
テスト ケース:
@Data public class Person { private String name; private String age; private Address address; @Test public void testCommonsBeanUtils(){ Person p1=PersonFactory.newPrototypeInstance(); try { Person p2=(Person) BeanUtils.cloneBean(p1); System.out.println("p1=" + p1); p2.getAddress().setType("Office"); System.out.println("p2=" + p2); } catch (Exception e) { e.printStackTrace(); } } }
cglib を使用してオブジェクトをコピーする
maven の依存関係:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.4</version> </dependency>
テスト ケース:
@Test public void testCglib(){ Person p1=PersonFactory.newPrototypeInstance(); BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, false); Person p2=new Person(); beanCopier.copy(p1, p2,null); p2.getAddress().setType("Office"); System.out.println("p1=" + p1); System.out.println("p2=" + p2); }
結果は衝撃的で、cglib は非常に素晴らしく、実際には浅いコピーです。ただし、cglib は拡張機能を提供します:
@Test public void testCglib(){ Person p1=PersonFactory.newPrototypeInstance(); BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, true); Person p2=new Person(); beanCopier.copy(p1, p2, new Converter(){ @Override public Object convert(Object value, Class target, Object context) { if(target.isSynthetic()){ BeanCopier.create(target, target, true).copy(value, value, this); } return value; } }); p2.getAddress().setType("Office"); System.out.println("p1=" + p1); System.out.println("p2=" + p2); }
Orika复制对象
orika的作用不仅仅在于处理bean拷贝,更擅长各种类型之间的转换。
maven依赖:
<dependency> <groupId>ma.glasnost.orika</groupId> <artifactId>orika-core</artifactId> <version>1.5.0</version> </dependency> </dependencies>
测试用例:
@Test public void testOrika() { MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); mapperFactory.classMap(Person.class, Person.class) .byDefault() .register(); ConverterFactory converterFactory = mapperFactory.getConverterFactory(); MapperFacade mapper = mapperFactory.getMapperFacade(); Person p1=PersonFactory.newPrototypeInstance(); Person p2 = mapper.map(p1, Person.class); System.out.println("p1=" + p1); p2.getAddress().setType("Office"); System.out.println("p2=" + p2); }
Spring BeanUtils复制对象
给Spring个面子,貌似它不支持深拷贝。
Person p1=PersonFactory.newPrototypeInstance(); Person p2 = new Person(); Person p2 = (Person) BeanUtils.cloneBean(p1); //BeanUtils.copyProperties(p2, p1);//这个更没戏
深拷贝性能对比
@Test public void testBatchDozer(){ Long start=System.currentTimeMillis(); Mapper mapper = new DozerBeanMapper(); for(int i=0;i<10000;i++){ Person p1=PersonFactory.newPrototypeInstance(); Person p2 = mapper.map(p1, Person.class); } System.out.println("dozer:"+(System.currentTimeMillis()-start)); //dozer:721 } @Test public void testBatchBeanUtils(){ Long start=System.currentTimeMillis(); for(int i=0;i<10000;i++){ Person p1=PersonFactory.newPrototypeInstance(); try { Person p2=(Person) BeanUtils.cloneBean(p1); } catch (Exception e) { e.printStackTrace(); } } System.out.println("commons-beanutils:"+(System.currentTimeMillis()-start)); //commons-beanutils:229 } @Test public void testBatchCglib(){ Long start=System.currentTimeMillis(); for(int i=0;i<10000;i++){ Person p1=PersonFactory.newPrototypeInstance(); BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, true); Person p2=new Person(); beanCopier.copy(p1, p2, new Converter(){ @Override public Object convert(Object value, Class target, Object context) { if(target.isSynthetic()){ BeanCopier.create(target, target, true).copy(value, value, this); } return value; } }); } System.out.println("cglib:"+(System.currentTimeMillis()-start)); //cglib:133 } @Test public void testBatchSerial(){ Long start=System.currentTimeMillis(); for(int i=0;i<10000;i++){ Person p1=PersonFactory.newPrototypeInstance(); Person p2=p1.deepClone(); } System.out.println("serializable:"+(System.currentTimeMillis()-start)); //serializable:687 } @Test public void testBatchOrika() { MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); mapperFactory.classMap(Person.class, Person.class) .field("name", "name") .byDefault() .register(); ConverterFactory converterFactory = mapperFactory.getConverterFactory(); MapperFacade mapper = mapperFactory.getMapperFacade(); Long start=System.currentTimeMillis(); for(int i=0;i<10000;i++){ Person p1=PersonFactory.newPrototypeInstance(); Person p2 = mapper.map(p1, Person.class); } System.out.println("orika:"+(System.currentTimeMillis()-start)); //orika:83 } @Test public void testBatchClone(){ Long start=System.currentTimeMillis(); for(int i=0;i<10000;i++){ Person p1=PersonFactory.newPrototypeInstance(); try { Person p2=(Person) p1.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } System.out.println("clone:"+(System.currentTimeMillis()-start)); //clone:8 }
(10k)性能比较:
//dozer:721 //commons-beanutils:229 //cglib:133 //serializable:687 //orika:83 //clone:8
深拷贝总结
原生的clone效率无疑是最高的,用脚趾头都能想到。
偶尔用一次,用哪个都问题都不大。
一般性能要求稍高的应用场景,cglib和orika完全可以接受。
另外一个考虑的因素,如果项目已经引入了某个依赖,就用那个依赖来做吧,没必要再引入一个第三方依赖。
以上がJavaオブジェクトコピーの詳細な例の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。