Java オブジェクトのクローン

Java オブジェクトのクローン

Java でオブジェクトのコピーを作成したい場合には、どうしたら良いでしょうか。

もし、オブジェクトのコピーを作りたいと思って、次のように変数 s1s2 に代入しても、同じオブジェクトへの参照がコピーされるだけになります。

  Shop s1 = new Shop("Ace Coffee");
  Shop s2 = s1;

変数 s2 は、もともと変数 s1 で参照していたオブジェクトを参照するようになるだけで、 オブジェクトのコピーが作成されるわけではありません。

Java オブジェクトのクローン

念のため補足しますと、int 型などのプリミティブのデータ型の場合であれば、変数を新しく宣言してそれに値を代入すれば、値がコピーできた、といえます。

次のような Shop クラスを定義して、試してみましょう。

class Shop {
  String name;

  public Shop(String name) {
    this.name = name;
  }

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

  public void printInfo(String tag) {
    System.out.printf("[%s] %s\n", tag, name);
  }
}

s1 としてオブジェクトを作成して、変数 s2 に単純に参照をコピーします。

public class TestApp {
  public static void main(String[] args) {
    Shop s1 = new Shop("Ace Coffee");
    Shop s2 = s1;

    s1.printInfo("s1");
    s2.printInfo("s2");

    s1.setName("Ace Ramen"); // 名前を書き換える

    s1.printInfo("s1");
    s2.printInfo("s2");
  }
}

実行結果

[s1] Ace Coffee
[s2] Ace Coffee
[s1] Ace Ramen
[s2] Ace Ramen

s1 を使って setName() メソッドを呼び出しましたが、 s2 が保持するオブジェクトも名前が変わっています。確かに同じオブジェクトを参照していることがわかります。

Java では「クローン」という操作をすることによって、オブジェクトの実体のコピーを作成します。

Java オブジェクトのクローン

Java オブジェクトのクローンを作成する方法

ここでは、上の Shop クラスを直して、クローンを作れるようにしてみましょう。

クローンをサポートするには、Java の Cloneable インターフェイスを実装します。

Java ではオブジェクトのクローンを作成することをサポートするために、Object クラスにて clone() メソッドが定義されています。 クラス定義で Cloneable インターフェイスを実装することを implements に明記し、clone() メソッドを実装します。

次の例のように、 Cloneable インターフェイスを実装します。

class Shop implements Cloneable {
  String name;

  public Shop(String name) {
    this.name = name;
  }

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

  public void printInfo(String tag) {
    System.out.printf("[%s] %s\n", tag, name);
  }

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

クローンを作成する側は次のようになります。

public class TestApp {
  public static void main(String[] args) throws CloneNotSupportedException {
    Shop s1 = new Shop("Ace Coffee");
    Shop s2 = s1.clone();

    s1.printInfo("s1");
    s2.printInfo("s2");

    s1.setName("Ace Ramen");

    s1.printInfo("s1");
    s2.printInfo("s2");
  }
}

4行目で clone() を呼び出しています。

実行結果

[s1] Ace Coffee
[s2] Ace Coffee
[s1] Ace Ramen
[s2] Ace Coffee

s1Ace Ramen に書き変わりましたが、 s2ABC Coffee のままです。

Object クラスの clone() メソッドは、デフォルトの動作としてクラスのメンバー (この場合 name) の値をコピーしたオブジェクトを返します。

clone() 呼び出し時に Cloneable インターフェイスをチェック

Java のシステムは clone() 呼び出し時に、Cloneable インターフェイスを実装しているか (implements Cloneable と記載されているか) チェックします。

もし、Cloneable インターフェイスが実装されていない場合、 CloneNotSupportedException 例外が発生します。

オブジェクトをメンバーに持つ場合のクローン作成に注意

clone() メソッドはクラスメンバーを単純にコピーします。このため、 メンバーにオブジェクトが設定されている場合は、やはり参照のコピーの問題が発生します。

従って、クローンをサポートする場合はそのメンバーも正しくクローンするように注意しましょう。

具体例は次のようになります。

先ほどの Shop クラスに、住所を表す Address オブジェクトを持たせるとします。

この場合、 Shop オブジェクトのクローンを行うときには、メンバーの Address オブジェクトもクローンします。

package com.keicode.java.test;

class Shop implements Cloneable {
  String name;
  Address address;

  public Shop(String name, Address address) {
    this.name = name;
    this.address = address;
  }

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

  public void setAddressState(String state) {
    this.address.setState(state);
  }

  public void printInfo(String tag) {
    System.out.printf("[%s] %s: %s\n",
        tag, name, address.getState());
  }

  @Override
  public Shop clone() throws CloneNotSupportedException {
    Shop clone = (Shop) super.clone();
    clone.address = this.address.clone();
    return clone;
  }
}

class Address implements Cloneable {
  String state;

  public Address(String state) {
    this.state = state;
  }

  public String getState() {
    return this.state;
  }

  public void setState(String state) {
    this.state = state;
  }

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

public class TestApp {
  public static void main(String[] args) throws CloneNotSupportedException {
    Address address = new Address("Tokyo");
    Shop s1 = new Shop("Ace Coffee", address);
    Shop s2 = s1.clone();

    s1.printInfo("s1");
    s2.printInfo("s2");

    s1.setName("Ace Ramen");
    s1.setAddressState("Hokkaido");

    s1.printInfo("s1");
    s2.printInfo("s2");
  }
}

実行結果

[s1] Ace Coffee: Tokyo
[s2] Ace Coffee: Tokyo
[s1] Ace Ramen: Hokkaido
[s2] Ace Coffee: Tokyo

以上、Java オブジェクトのクローンについて、実装例を混えて説明しました。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Java 入門