Javaにおけるオブジェクトの複製(clone) - Object#cloneのオーバーライド

オブジェクト複製の方法 その1 cloneメソッド

デフォルトのcloneメソッドは、Objectクラスに定義されている、protectedのメソッドです。

全てのクラスは暗黙的にObjectクラスを継承しているため、cloneメソッドを呼び出すことで自身の複製を 生成することが出来ます。

cloneメソッドの具体的な実装は以下の手順に従ってください。

  1. 複製するオブジェクトのクラスはCloneableインターフェースをimplementsする。
  2. 複製するオブジェクトのクラスでObjectクラスのcloneメソッドをpublicでオーバーライドする。
    (Objectクラスで定義されているcloneメソッドはprotectedのため、実質的に直接呼び出すことができない。)
  3. 深いコピーを生成する必要がある場合は、オーバーライドしたcloneメソッド内で処理を記述する。


	/**
	 * 複製を生成可能なUserクラス。
	 *
	 */
	class User implements Cloneable { // ← Cloneableを実装せずにcloneメソッドを呼び出すと
	                                    //   CloneNotSupportedExceptionがスローされる

		/** ID */
		private int id;

		/** 名前 */
		private String name;

		/** お誕生日 */
		private Date birthDate;

		// ~~ 省略 ~~

		// Object#cloneはprotectedのため、デフォルトで呼び出すことができない。
		// したがって、このサンプルのように複製を生成するオブジェクトのクラスで
		// cloneメソッドをオーバーライドしなければならない

		/**
		 * このクラスのインスタンスの複製(深いコピー)を生成して返します。
		 * 
		 * @return このクラスのインスタンスの複製
		 */
		public Object clone() {
			try {
				User cloneUser = (User) super.clone();
				cloneUser.setBirthDate(new Date(this.birthDate.getTime())); // ← 深いコピー
				return cloneUser;
			} catch (CloneNotSupportedException e) {
				// 省略
			}
		}
	}
					

上記実装例でsuper.clone()を呼び出しただけでは、birthDateは複製元と複製先で共有します

よって、デフォルトのsuper.clone()メソッド呼び出しのみでは、複製元、複製先のいずれかでbirthDateの値を変更した場合(非常に現実的な問題として、誕生日のが変更されるなどということはありえ無いけれども)、互いに影響を及ぼしあってしまい、複製を生成した意味がなくなってしまいます。

上記の理由から、birthDateの値については、birthDateと同じ値を保持する新しいDate型のオブジェクトを生成し、birthDateの値を新しいオブジェクトで置き換えています。

実装例のように、深いコピーを生成しなければならないのは、複製したいオブジェクトが可変オブジェクトを状態として保持しているためです。不変オブジェクトを状態として保持しているだけならば、super.clone()を呼び出し浅いコピーを得るだけで十分です。なぜなら、複製元と複製先のオブジェクトで不変オブジェクトを共有したとしても、不変オブジェクトの値を変更することはできないため、実質不変オブジェクトの複製を生成する必然性がないからです。

実装例のフィールド値の一つである"name"はString型の値であり、String型のインスタンスは不変オブジェクトです。実装したcloneメソッド内で"name"のコピーを生成していないのは上記の理由によります。

浅いコピーと深いコピーについてはこちらを参考にしてください。

また、上記クラス使用例はこちら