読者です 読者をやめる 読者になる 読者になる

うさがにっき

読書感想文とプログラムのこと書いてきます

cloneメソッドについて

概要

オブジェクトを複製するcloneメソッドについて知らなかったことを書く

詳細

cloneメソッド

オブジェクトを複製する際以下のようなコードを書くとバグる

Book b1 = new Book("Java入門", 3000);
Book b2 = b1;
System.out.println(b2.toString());
// 結果:Java入門 3000円
b1.setPrice(2000);
System.out.println(b1.toString());
// 結果:Java入門 2000円

オブジェクトの参照が複製されているため、複製元も値が変わってしまう

オブジェクトの複製を作成するにはcloneメソッドを使う
cloneメソッドはオブジェクトの内容をフィールド単位でコピーするので、上記のような問題を回避できる
cloneメソッドメソッドを使うにはCloneableインタフェースを実装する必要が有る

フィールドが基本型と文字列型のみのオブジェクトの場合
public class Book implements Cloneable {
	private String title;
	private int price;

	// 中略

	@Override
	public Book clone() {
		try {
			// 呼び出し元で簡単に扱うようにキャストしてから返す
			return (Book)super.clone();
		}catch(CloneNotSupportedException e) {
			e.printStackTrace();
		}

		return null;
	}
}
フィールドに参照型を含む場合

cloneメソッドの挙動はフィールドを個々にコピーするだけなので、コピー元に参照型のフィールドがあった場合、コピー元とコピー先が同じものを見てしまう
そのためcloneメソッドに追加のロジックが必要になる

public class Book implements Cloneable {
	private String title;
	private int price;
	private String[] authors
	// 中略

	@Override
	public Book clone() {
		Book2 result = null;
		try {
			result = (Book2)super.clone();
			// フィールドオブジェクトのclone
			result.authors = this.authors.clone();
		}catch(CloneNotSupportedException e) {
			e.printStackTrace();
		}

		return result;
	}
}
配列のコピー

配列のコピーにもcloneメソッドは利用できるが、よりパフォーマンスに優れ、配列長の指定や、部分的なコピーにも対応したArrays.copyOf, copyOfRangeを利用するべき

int[] list1 = {1, 2, 4};
int[] list2 = Arrays.copyOf(list1, list1.length);
int[] list3 = Arrays.copyOfRange(list1, 1, 2);

list1[0] = 10;
System.out.println(Arrays.toString(list1));	//[10, 2, 4]
System.out.println(Arrays.toString(list2));	//[1, 2, 4]
System.out.println(Arrays.toString(list3));	//[2]

ただし、copyOf / copyOfRangeメソッドはcloneメソッドと同じくシャローコピーである点に注意
つまり、要素が参照型である場合、その内容の変更はコピー先にも影響を与えてしまう
そのようなケースではfor文などを使い個別にコピーする必要がある、このようなコピーの仕方をディープコピーという

参考

AndroidエンジニアのためのモダンJava

AndroidエンジニアのためのモダンJava