Javaでオブジェクトをコピーする際、単に = で代入したり、標準の clone() を使ったりすると、思わぬバグを引き起こすことがあります。その原因である「シャローコピー(浅いコピー)」と、解決策としての「ディープコピー(深いコピー)」について解説します。
1. シャローコピー(Shallow Copy / 浅いコピー)
仕組み
シャローコピーは、オブジェクトの**「参照(メモリ上の住所)」だけ**をコピーします。
コピー元とコピー先は、メモリ上の同じ実体(インスタンス)を指している状態です。
Javaでの実装例
class Person {
String name;
Person(String name) { this.name = name; }
}
public class Main {
public static void main(String[] args) {
Person alice = new Person("Alice");
// シャローコピー(参照のコピー)
Person copyAlice = alice;
copyAlice.name = "Bob"; // コピー側を書き換えると...
System.out.println(alice.name); // Bob(元の方も変わってしまう!)
}
}
なぜ問題か?:意図せずオリジナルデータが破壊されるため、バグの特定が非常に困難になります。
2. ディープコピー(Deep Copy / 深いコピー)
仕組み
ディープコピーは、メモリ上に実体そのものを新しく作成し、値をコピーします。
コピー元とコピー先は完全に別の住所を持つため、互いに影響を与えません。
Javaでの推奨実装:コピーコンストラクタ
class Person {
String name;
Person(String name) { this.name = name; }
// コピーコンストラクタ(自分と同じ型を受け取って新しく作る)
Person(Person other) {
this.name = other.name;
}
}
3. 【深掘り】clone()メソッドは使わないのが正解?
Javaには標準で Object.clone() というメソッドが存在しますが、現在では**「使用を避けるべき」**というのが一般的なエンジニアの共通認識です。
なぜ clone() は避けられるのか?
Cloneableインターフェースの設計不備: インターフェースなのにメソッドが定義されておらず、振る舞いが特殊。
例外処理の強制: 常に
CloneNotSupportedExceptionをキャッチする必要があり、コードが汚れる。シャローコピーのリスク: デフォルトの
super.clone()はシャローコピーを行うため、結局自分で深いコピーの実装を書く手間が変わらない。
結論: 新しくコードを書くなら、コピーコンストラクタか、ファクトリメソッドを作成するのがJavaのベストプラクティスです。
4. 不変(Immutable)オブジェクトならコピーは不要
「コピーしなければならない状況」を物理的に無くすのが、最も賢い設計です。それが不変(Immutable)オブジェクトです。
仕組み
一度作成したら中身を変更できないクラスにすれば、シャローコピーをしても副作用が起きません。
Java標準の例:
StringやIntegerなど。自作の例: フィールドを
finalにし、セッター(setter)を作らない。
public final class ImmutableUser {
private final String name; // finalで書き換え禁止
public ImmutableUser(String name) { this.name = name; }
public String getName() { return name; }
}
メリット:
どこに渡しても中身が変わる心配がないため、コピーを作る必要自体がなくなります。
メモリ消費を抑えられ、スレッドセーフ(マルチスレッドでも安全)になります。
まとめ:使い分けの指針
| 状況 | 採用すべき手法 |
| 基本の代入 | シャローコピー(ただし変更しないことが前提) |
| 元データを保護して編集したい | ディープコピー(コピーコンストラクタ推奨) |
| クラスを自作する場合 | 可能な限り「不変(Immutable)」にする |
clone() の複雑さに悩まされるより、「不変にする」あるいは「新しく作る(コピーコンストラクタ)」というシンプルな戦略をとることが、堅牢なJavaプログラムへの近道です。
0 件のコメント:
コメントを投稿