JavaのObjectクラスのclone()メソッド – Javaにおけるクローニング

クローニングとは、オブジェクトのコピーを作るプロセスです。JavaのObjectクラスには、既存のインスタンスのコピーを返すネイティブなclone()メソッドが付属しています。JavaではObjectがベースクラスであるため、デフォルトで全てのオブジェクトがクローニングをサポートしています。

Javaのオブジェクト複製

java clone object
package com.scdev.cloning;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class Employee implements Cloneable {

	private int id;

	private String name;

	private Map<String, String> props;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

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

	public Map<String, String> getProps() {
		return props;
	}

	public void setProps(Map<String, String> p) {
		this.props = p;
	}

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

}

私たちはObject clone()メソッドを使用しており、そのためにCloneableインターフェースを実装しています。私たちはスーパークラスのclone()メソッド、つまりObject clone()メソッドを呼び出しています。

オブジェクトの clone() メソッドを使用する

インスタンスのコピーを作成するために、オブジェクトのclone()メソッドを使用するテストプログラムを作成しましょう。

package com.scdev.cloning;

import java.util.HashMap;
import java.util.Map;

public class CloningTest {

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

		Employee emp = new Employee();

		emp.setId(1);
		emp.setName("Pankaj");
		Map<String, String> props = new HashMap<>();
		props.put("salary", "10000");
		props.put("city", "Bangalore");
		emp.setProps(props);

		Employee clonedEmp = (Employee) emp.clone();

		// Check whether the emp and clonedEmp attributes are same or different
		System.out.println("emp and clonedEmp == test: " + (emp == clonedEmp));
		
		System.out.println("emp and clonedEmp HashMap == test: " + (emp.getProps() == clonedEmp.getProps()));
		
		// Let's see the effect of using default cloning
		
		// change emp props
		emp.getProps().put("title", "CEO");
		emp.getProps().put("city", "New York");
		System.out.println("clonedEmp props:" + clonedEmp.getProps());

		// change emp name
		emp.setName("new");
		System.out.println("clonedEmp name:" + clonedEmp.getName());

	}

}

出力:

emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: true
clonedEmp props:{city=New York, salary=10000, title=CEO}
clonedEmp name:Pankaj

ランタイムでのCloneNotSupportedException

もし私たちのEmployeeクラスがCloneableインターフェースを実装しない場合、上記のプログラムはCloneNotSupportedExceptionの実行時例外をスローします。

Exception in thread "main" java.lang.CloneNotSupportedException: com.scdev.cloning.Employee
	at java.lang.Object.clone(Native Method)
	at com.scdev.cloning.Employee.clone(Employee.java:41)
	at com.scdev.cloning.CloningTest.main(CloningTest.java:19)
Java CloneNotSupportedException

オブジェクトのクローニングを理解する

上記の出力を調べて、Objectのclone()メソッドの動作を理解しましょう。

    1. empとclonedEmp == test: false: これは、empとclonedEmpが同じオブジェクトを参照していないことを意味します。これは、Javaのオブジェクトのクローニングの要件と一致しています。

 

    1. empとclonedEmpのHashMap == test: true: したがって、empとclonedEmpのオブジェクト変数は同じオブジェクトを参照しています。これは、基になるオブジェクトの値を変更すると、深刻なデータ整合性の問題になる可能性があります。値の変更はクローンされたインスタンスにも反映される可能性があります。

 

    1. clonedEmpのプロパティ:{city=New York, salary=10000, title=CEO}:clonedEmpのプロパティに変更は加えていませんが、empとclonedEmpの変数は同じオブジェクトを参照しているため、変更が加えられました。これは、デフォルトのObjectクラスのclone()メソッドがシャローコピーを作成するためです。クローニングプロセスで完全に切り離されたオブジェクトを作成したい場合には問題になることがあります。そのため、Objectクラスのclone()メソッドを適切にオーバーライドする必要があります。

 

    clonedEmpの名前:Pankaj:何が起こったのでしょうか?empの名前を変更しましたが、clonedEmpの名前は変わりませんでした。これは、Stringが不変であるためです。したがって、empの名前を設定すると、新しい文字列が作成され、empの名前の参照がこれ.name = name;で変更されます。したがって、clonedEmpの名前は変わりません。同様の動作は、プリミティブ型の変数に対しても見られます。したがって、オブジェクト内にプリミティブ型と不変変数しかない場合、Javaのオブジェクトのデフォルトのクローニングは問題ありません。

オブジェクトの複製の種類 (Obujekuto no fukusei no shurui)

オブジェクトのクローニングには、シャロークローニングとディープクローニングの2つのタイプがあります。それぞれを理解し、Javaプログラムでクローニングを実装する最良の方法を見つけましょう。

1. 浅いクローニング

JavaのObjectクラスのclone()メソッドのデフォルト実装は、浅いコピーを使用しています。このメソッドは、インスタンスのコピーを作成するために反射APIを使用しています。以下のコードスニペットは、浅いコピーの実装を示しています。

@Override
 public Object clone() throws CloneNotSupportedException {
 
	 Employee e = new Employee();
	 e.setId(this.id);
	 e.setName(this.name);
	 e.setProps(this.props);
	 return e;
}

2. ディープクローニング

深いクローニングでは、フィールドを1つずつコピーする必要があります。もしリストやマップなどのネストされたオブジェクトを持つフィールドがある場合は、それらを1つずつコピーするコードを書かなければなりません。だからこそ、これは深いクローニングや深いコピーと呼ばれています。以下のコードのように、Employeeのcloneメソッドをオーバーライドすることで深いクローニングを実現できます。

public Object clone() throws CloneNotSupportedException {

	Object obj = super.clone(); //utilize clone Object method

	Employee emp = (Employee) obj;

	// deep cloning for immutable fields
	emp.setProps(null);
	Map<String, String> hm = new HashMap<>();
	String key;
	Iterator<String> it = this.props.keySet().iterator();
	// Deep Copy of field by field
	while (it.hasNext()) {
		key = it.next();
		hm.put(key, this.props.get(key));
	}
	emp.setProps(hm);
	
	return emp;
}

このclone()メソッドの実装により、私たちのテストプログラムは以下の出力を生成します。

emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: false
clonedEmp props:{city=Bangalore, salary=10000}
clonedEmp name:Pankaj

ほとんどの場合、これが私たちの望むことです。clone()メソッドは、元のインスタンスと完全に切り離された新しいオブジェクトを返すべきです。したがって、プログラムでObject cloneとクローニングを使用しようと思っている場合は、注意深くオーバーライドし、可変フィールドに気を付けて適切に行ってください。クラスが他のクラスを拡張し、さらに他のクラスを拡張している場合、これは困難な作業になる可能性があります。すべての可変フィールドのディープコピーを行うために、Objectの継承階層全体を辿る必要があります。

シリアライズを使用したクローニング? (Shiriarai o shiyō shita kurōningu?)

手軽に深いクローンを作成する方法の一つは、シリアライズを使用することです。しかし、シリアライズは負荷が高い手続きであり、クラスはSerializableインタフェースを実装する必要があります。また、全てのフィールドとスーパークラスもSerializableを実装しなければなりません。

Apache Commons Util を使用する

もしすでにプロジェクトでApache Commons Utilのクラスを使用しており、さらにそのクラスがシリアライズ可能である場合、以下のメソッドを使用してください。

Employee clonedEmp = org.apache.commons.lang3.SerializationUtils.clone(emp);

コピーのためのクローニング用コンストラクタ

オブジェクトのコピーを作成するために、コピーコンストラクタを定義することができます。オブジェクトのclone()メソッドに依存する必要はありません。例えば、次のコードのように、Employeeのコピーコンストラクタを持つことができます。

public Employee(Employee emp) {
	
	this.setId(emp.getId());
	this.setName(emp.getName());
	
	Map<String, String> hm = new HashMap<>();
	String key;
	Iterator<String> it = emp.getProps().keySet().iterator();
	// Deep Copy of field by field
	while (it.hasNext()) {
		key = it.next();
		hm.put(key, emp.getProps().get(key));
	}
	this.setProps(hm);

}

従業員オブジェクトのコピーが必要な場合は、Employee clonedEmp = new Employee(emp); を使用して取得することができます。ただし、クラスに多くの変数、特にプリミティブ型や不変型がある場合、コピーのコンストラクタを書くことは面倒な作業になることがあります。

以下のJavaオブジェクトのクローニングのベストプラクティスについて述べます。

    1. デフォルトのObjectのclone()メソッドは、クラスがプリミティブ型と不変の変数を持っている場合や、シャローコピーが必要な場合にのみ使用してください。継承の場合は、オブジェクトのレベルまで拡張しているすべてのクラスをチェックする必要があります。

また、クラスのほとんどが可変のプロパティを持っている場合は、コピーコンストラクタを定義することもできます。

オーバーライドされたcloneメソッドでsuper.clone()を呼び出すことで、Objectのclone()メソッドを利用し、可変フィールドのディープコピーのための必要な変更を行うことができます。

クラスがシリアライズ可能であれば、クローニングにはシリアライズを使用することができます。ただし、パフォーマンスに影響があるため、クローニングにシリアライズを使用する前にベンチマーキングを行ってください。

クラスを拡張していて、ディープコピーを使用してcloneメソッドが適切に定義されている場合、デフォルトのcloneメソッドを利用することができます。たとえば、Employeeクラスで適切に定義されたclone()メソッドが次のようになっている場合です。

@Override
public Object clone() throws CloneNotSupportedException {

Object obj = super.clone();

Employee emp = (Employee) obj;

// イミュータブルフィールドのディープコーピィング
emp.setProps(null);
Map<String, String> hm = new HashMap<>();
String key;
Iterator it = this.props.keySet().iterator();
// フィールドごとのディープコピー
while (it.hasNext()) {
key = it.next();
hm.put(key, this.props.get(key));
}
emp.setProps(hm);

return emp;
}

次のように、子クラスを作成して、スーパークラスのディープコーピィングを利用することができます。

package com.scdev.cloning;

public class EmployeeWrap extends Employee implements Cloneable {

private String title;

public String getTitle() {
return title;
}

public void setTitle(String t) {
this.title = t;
}

@Override
public Object clone() throws CloneNotSupportedException {

return super.clone();
}
}

EmployeeWrapクラスには可変のプロパティはありませんし、スーパークラスのclone()メソッドの実装を利用しています。可変フィールドがある場合は、それらのフィールドのディープコーピィングに注意する必要があります。次に、このクローニング方法が正常に機能するかどうかをテストする簡単なプログラムがあります。

package com.scdev.cloning;

import java.util.HashMap;
import java.util.Map;

public class CloningTest {

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

EmployeeWrap empWrap = new EmployeeWrap();

empWrap.setId(1);
empWrap.setName(“Pankaj”);
empWrap.setTitle(“CEO”);

Map<String, String> props = new HashMap<>();
props.put(“salary”, “10000”);
props.put(“city”, “Bangalore”);
empWrap.setProps(props);

EmployeeWrap clonedEmpWrap = (EmployeeWrap) empWrap.clone();

empWrap.getProps().put(“1”, “1”);

System.out.println(“empWrapの可変プロパティの値 = ” + empWrap.getProps());

System.out.println(“clonedEmpWrapの可変プロパティの値 = ” + clonedEmpWrap.getProps());

}

}

出力:
empWrapの可変プロパティの値 = {1=1, city=Bangalore, salary=10000}
clonedEmpWrapの可変プロパティの値 = {city=Bangalore, salary=10000}

予想どおりに動作しました。

それがJavaにおけるオブジェクトのクローニングに関するすべてです。JavaのObject clone()メソッドと、それを適切にオーバーライドして何らかの不良効果を避ける方法について、少しでも理解できていればいいと思います。

私のGitHubリポジトリからプロジェクトをダウンロードすることができます。

参照:Object cloneのAPIドキュメント

コメントを残す 0

Your email address will not be published. Required fields are marked *