The HIRO Says

If you smell what The HIRO is cooking!!!

clone() メソッドの実装(1)−基本的な実装方法

Java で Prototype パターンを実現するために必要な、clone() メソッドについて記述します。


clone() メソッドの仕様

いずれも、必須ではないそうです。(´・ω・)

  1. x.clone() != x を満たすこと。
  2. x.clone().getClass() == x.getClass() を満たすこと。
  3. x.clone().equals(x) = true であること。
  4. コンストラクタを呼ばずにインスタンスを作ること。

clone() メソッドの特殊性

clone() メソッドは、これまで説明した equals() メソッドや hashCode() メソッドとは異なる特徴を持っています。
一筋縄ではいかないようになっています。

  1. 親クラス(java.lang.Object)では、protected で定義されている。
  2. java.lang.Cloneable インターフェースを implement しないで super.clone() を呼び出すと、CloneNotSupportedException をスローする!

基本的な実装方法

  1. java.lang.Cloneable インターフェースを implement します。
  2. 親クラス(特になければ java.lang.Object)の clone() メソッドを、public でオーバーライドします。
  3. super.clone() を呼び出します。(primitive 型や immutable オブジェクトの参照は、これだけでコピー完了です。)
  4. mutable なオブジェクトの参照は、そのオブジェクトの clone() メソッドを再帰的に呼び出してあげます。
  5. CloneNotSupportedException は、無理にスローしなくてOKです。

実装例

import java.util.Date;

// ●Cloneable を実装すること!
public class ComplicateObject implements Cloneable {

// fields
    private boolean booleanValue;
    private byte    byteValue;
    private char    charValue;
    private short   shortValue;
    private int     intValue;
    private long    longValue;
    private float   floatValue;
    private double  doubleValue;
    private String  stringValue;
    private Date    dateValue;


    @Override
    public ComplicateObject clone() {
        // J2SE 1.5 以降は、当クラスのインスタンスを直接返してよい。
        // こうすれば、client 側で毎回キャストする必要がなくなり便利。

        ComplicateObject result = null;

        try {
            // super.clone() が、各フィールドをコピーしてくれる。
            result = (ComplicateObject) super.clone();

            // オブジェクトの参照の場合、clone() を再帰呼び出しすればよい(deep copy)。
            // ※String は immutable なので、特別 clone() を呼ぶ必要はない。そもそも clone() を呼び出せない。
            result.dateValue = (Date) this.dateValue.clone();

        } catch (CloneNotSupportedException cnse) {
            // Cloneable を implement しないと発生する。通常発生しない。
            // Cloneable を implement すれば発生しないことを仕様として検証済なので、特にテストは不要。
            // ※定義上発生しない状態にしておけば、client への負担を減らせるので吉。
        }

        return result;
    }


// getter/setter/constructor は省略
}

動作確認

以下のテストケースは、正常に動作します。

    public void testClone() {

        ComplicateObject original = new ComplicateObject();
        original.setBooleanValue(true);
        original.setByteValue*1;
        assertTrue(original.equals(copy));

        // copy した値の検証
        assertTrue(copy.getBooleanValue());
        assertEquals(
                1,
                copy.getByteValue());
        assertEquals(
                '2',
                copy.getCharValue());
        assertEquals(
                3,
                copy.getShortValue());
        assertEquals(
                4,
                copy.getIntValue());
        assertEquals(
                5,
                copy.getLongValue());
        assertEquals(
                6.0f,
                copy.getFloatValue());
        assertEquals(
                7.0,
                copy.getDoubleValue());
        assertEquals(
                "8",
                copy.getStringValue());
        assertEquals(
                date,
                copy.getDateValue());
        // deep copy の検証
        assertFalse(date == copy.getDateValue());
    }

補足

clone() メソッド の基本的な実装は、上記で特に問題ないです。
ですが、clone() メソッドには色々なテクニックがありますので、後日それらについて触れていこうと思います。

*1:byte)1); original.setCharValue('2'); original.setShortValue((short)3); original.setIntValue(4); original.setLongValue(5); original.setFloatValue(6); original.setDoubleValue(7); original.setStringValue("8"); Date date = new Date(); original.setDateValue(date); // copy ComplicateObject copy = original.clone(); // 規約に合致していることの検証 assertFalse(original == copy); assertEquals( original.getClass(), copy.getClass(