ホームページ Java &#&チュートリアル Javaオブジェクトコピーの詳細な例

Javaオブジェクトコピーの詳細な例

May 02, 2017 pm 02:15 PM

この記事では、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=北京))
ログイン後にコピー

クローン メソッドのディープ コピーの概要

① カスタム オブジェクトのメンバーなど、非ネイティブ メンバーがある場合は、次のものが必要です:

  1. このメンバーは Cloneable インターフェイスを実装し、 clone() メソッドをオーバーライドします。このメソッドを public に昇格させることを忘れないでください。

  2. 同時に、コピーしたクラスの 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 サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Javaの平方根 Javaの平方根 Aug 30, 2024 pm 04:26 PM

Java の平方根のガイド。ここでは、Java で平方根がどのように機能するかを、例とそのコード実装をそれぞれ示して説明します。

Javaの完全数 Javaの完全数 Aug 30, 2024 pm 04:28 PM

Java における完全数のガイド。ここでは、定義、Java で完全数を確認する方法、コード実装の例について説明します。

Java の乱数ジェネレーター Java の乱数ジェネレーター Aug 30, 2024 pm 04:27 PM

Java の乱数ジェネレーターのガイド。ここでは、Java の関数について例を挙げて説明し、2 つの異なるジェネレーターについて例を挙げて説明します。

Javaのアームストロング数 Javaのアームストロング数 Aug 30, 2024 pm 04:26 PM

Java のアームストロング番号に関するガイド。ここでは、Java でのアームストロング数の概要とコードの一部について説明します。

ジャワのウェカ ジャワのウェカ Aug 30, 2024 pm 04:28 PM

Java の Weka へのガイド。ここでは、weka java の概要、使い方、プラットフォームの種類、利点について例を交えて説明します。

Javaのスミス番号 Javaのスミス番号 Aug 30, 2024 pm 04:28 PM

Java のスミス番号のガイド。ここでは定義、Java でスミス番号を確認する方法について説明します。コード実装の例。

Java Springのインタビューの質問 Java Springのインタビューの質問 Aug 30, 2024 pm 04:29 PM

この記事では、Java Spring の面接で最もよく聞かれる質問とその詳細な回答をまとめました。面接を突破できるように。

Java 8 Stream Foreachから休憩または戻ってきますか? Java 8 Stream Foreachから休憩または戻ってきますか? Feb 07, 2025 pm 12:09 PM

Java 8は、Stream APIを導入し、データ収集を処理する強力で表現力のある方法を提供します。ただし、ストリームを使用する際の一般的な質問は次のとおりです。 従来のループにより、早期の中断やリターンが可能になりますが、StreamのForeachメソッドはこの方法を直接サポートしていません。この記事では、理由を説明し、ストリーム処理システムに早期終了を実装するための代替方法を調査します。 さらに読み取り:JavaストリームAPIの改善 ストリームを理解してください Foreachメソッドは、ストリーム内の各要素で1つの操作を実行する端末操作です。その設計意図はです

See all articles