0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Effective Java 3-13:cloneを注意してオーバーライドする

Posted at

3-13:cloneを注意してオーバーライドする

要点

Cloneable/Object.clone()は「浅いフィールドコピー」を行い、継承や可変フィールドで簡単に破綻するので、clone() を公開するなら正しく実装して深いコピーや例外処理を行う。ただし多くの場合はコピーコンストラクタ/静的ファクトリを使う方が安全で分かりやすい。

なぜcloneは扱いが難しいか

  • Cloneable はマーカーインタフェース:実際のコピー処理は Object.clone() による。

  • Object.clone() は**フィールドごとのビットコピー(shallow copy)**を行う — mutable なフィールドは同じ参照を共有してしまう。

  • Object.clone() はコンストラクタを通さずにオブジェクトを生成する(初期化ルールを飛ばす)。

  • 継承階層で各クラスが clone を「協力」して実装しないと不整合が起きる(super.clone() を呼ぶべきか、どう深いコピーするか等)。

  • 例外(CloneNotSupportedException)の扱いや返り値の型(covariant return)など細かい実装上の注意が多い。

悪い例

public class Point {
    int x, y;
    @Override
    public Object clone() { // 警告:返り値は Object、例外未処理
        return new Point(x, y); // 手作りコピーはまだマシだが慣習に反する
    }
}

問題点:

  • clone() のシグネチャが標準と違う(public Object clone() は古く、例外処理がない/互換性の問題)。
  • mutable なフィールド(配列、リスト、他のオブジェクト)を浅くコピーしてしまうと共有が起きる(バグ)。
  • さらに典型的に危険なのが、super.clone() を呼ばずに new でインスタンスを作る実装や、Cloneable を実装していないのに Object.clone() に依存する実装など。

良い例:安全に clone() を実装するパターン

シンプルな不変・浅コピーで許されるケース(final クラス)

public final class Point implements Cloneable {
    private final int x, y;
    public Point(int x, int y) { this.x = x; this.y = y; }

    @Override
    public Point clone() {
        try {
            return (Point) super.clone(); // shallow copy で十分(不変なので安全)
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(); // 起きないはず
        }
    }
}
  • クラスが final でフィールドが不変なら super.clone()(浅いコピー)で問題ない。CloneNotSupportedException は起きないので AssertionError に変換するのが一般的。

 
mutable フィールド(深いコピーが必要な場合)

public class Person implements Cloneable {
    private String name;
    private int[] tags; // mutable

    public Person(String name, int[] tags) {
        this.name = name;
        this.tags = tags;
    }

    @Override
    public Person clone() {
        try {
            Person p = (Person) super.clone(); // shallow copy
            p.tags = tags == null ? null : tags.clone(); // deep copy of mutable field
            return p;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

ポイント:

  • super.clone() でまず浅いコピー(同じクラスの正しい型を得る)を得る。
  • その後、自分のクラスが持つ 可変フィールド を個別にコピーして「深いコピー」を作る。
  • 例外は通常 AssertionError に置き換える。

継承が絡むともっと難しい

  • サブクラス・スーパークラスがそれぞれ clone() を正しく協力して実装しないと危険。
  • 設計上、クラス階層に clone を公開するのは難しい(各クラスが clone を理解して協調する必要あり)。
    → つまり継承を考慮するなら、多くの開発者は clone() を避ける。

例外処理とシグネチャ(推奨)

  • CloneNotSupportedException はチェック例外なので、clone() を public にする場合は通常キャッチして AssertionError(起きないはず)に変換しておく。
  • 返り値は covariant return を使って具体クラス型を返す(public Person clone())と使いやすい。

なぜ copy constructor / static factory を推すのか(Effective Java の推奨)

  • コピーコンストラクタ(new Person(other))や静的ファクトリ(Person.copyOf(other))は 分かりやすく堅牢。
  • コンストラクタ/ファクトリならコンストラクタの通常の初期化ルールが走る(clone はコンストラクタを通らない)。
  • 継承に対しても扱いやすく、例外処理も単純。
    → 可能なら clone ではなくコピーコンストラクタ/static factory を使うべき。

例(コピーコンストラクタ):

public class Person {
    private String name;
    private int[] tags;

    public Person(Person original) {
        this.name = original.name;
        this.tags = original.tags == null ? null : original.tags.clone();
    }
}

まとめ

  • clone() は正しく実装するのが難しい(浅いコピー、継承、例外の扱い、コンストラクタを経ない生成)。
  • やむを得ない場合(パフォーマンスや既存 API との互換が理由)には、super.clone() を使ってから mutable フィールドを個別に深コピーし、CloneNotSupportedException は AssertionError に変換するパターンを使う。
  • 可能ならコピーコンストラクタや静的ファクトリを使う(これが推奨)。

すぐ使える実装テンプレート(コピーコンストラクタ版と clone() 版)

推奨:コピーコンストラクタ(読みやすく安全)

public class Foo {
    private final int[] data;

    public Foo(Foo original) {
        this.data = original.data == null ? null : original.data.clone();
    }
}

必要で clone を実装するなら(最小限の安全パターン)

public class Foo implements Cloneable {
    private int[] data;

    @Override
    public Foo clone() {
        try {
            Foo f = (Foo) super.clone();
            f.data = data == null ? null : data.clone();
            return f;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?