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

Cloneable

Cloneableはお世辞にも使いやすいとはいえないので、もうちょっと使いやすくしようと試みる。

public interface Copyable<E extends Cloneable> extends Cloneable {
    E copy();
    Object clone() throws CloneNotSupportedException;
}


このインターフェイスを実装したクラスは、copyはもちろん、cloneもpublicとして実装しなければならない。
でも、既存のクラスにこのインターフェイスを実装させるのは非現実的*1なので、Copyableへの変換を行うクラスを作成。

public final class CopyUtils {

    private CopyUtils() {}
    
    public static <E extends Cloneable> Copyable<E> toCopyable(E obj) {
        try {
            Method clone = obj.getClass().getMethod("clone");
            return new CopyableImpl<E>(clone, obj);
        } catch (SecurityException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }
    
    @SuppressWarnings("unchecked")
    private static <E extends Cloneable> E invoke(Method method, E obj) {
        try {
            return (E) method.invoke(obj);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e.getCause());
        }
    }
    
    private static final class CopyableImpl<E extends Cloneable> implements Copyable<E> {
        private final Method clone;
        private final E obj;
        CopyableImpl(Method clone, E obj) {
            this.clone  = clone;
            this.obj    = obj;
        }
        
        @Override
        public Object clone() { return invoke(clone, obj); }
        
        public E copy() { return invoke(clone, obj); }
    }
}


toCopyableメソッドでは引数で渡されたオブジェクトからcloneメソッドを表すMethodオブジェクトを取得して、引数で渡されたオブジェクトをCopyableImplでラップして返すだけ。
このメソッドが外部から呼び出されるメソッドで、引数はCloneableを実装しているオブジェクトに制限されるので、Cloneableを実装していないクラスのオブジェクトを渡そうとするとコンパイル時にはじかれる。


invokeメソッドはチェックされる例外をRuntimeExceptionに変換して再送出しているだけ。ここでEにキャストしているけど、これは仕方がない・・・と思う*2


CopyableImplクラスは文字通りCopyableの実装クラスとなる。このクラスはコンストラクタで受け取ったMethodオブジェクトとCloneableを実装したオブジェクトを保持していて、copyかcloneが呼び出されるとinvokeメソッドを呼び出す。
invokeメソッドをこのクラスの中におかなかったのは、単にネストをあまり深くしたくなかっただけで、あまり意味はない。


で、これを使うと次のように書ける。

// Copyableを実装しているクラスは一番単純
SomeCopyableClass copyable = ...
SomeCopyableClass copyableCopy = copyable.copy();

// Cloneableを実装しているクラスは変換してからコピー
Date date = new Date();
Copyable<Date> copyable = CopyUtils.toCopyable(date);
Date dateCopy = copyable.copy();

// ObjectクラスはCloneableを実装していないので、コンパイルエラー
// CopyUtils.toCopyable(new Object());


Javaジェネリクスが次のように書けたら

// CloneableかNumberを実装/継承したE
public interface Copyable<E extends Cloneable | Number> extends Cloneable {
    E copy();
    Object clone() throws CloneNotSupportedException;
}

いろいろと面白そうだなぁ、とも思ってみたり。リフレクションを使えばこんな感じのことも出来るけど、やっぱりコンパイル時にエラー検出したいからなぁ。


と、ここまでやってみたものの、Effective Javaに書かれているように、通常はコピーコンストラクタを用意したほうがいいというのがなんとも・・・


あぁねむい。なんでこんなに眠いんだ。

*1:出来る出来ないは別として、ライセンスとかの問題もある

*2:あー、未だにJavaジェネリクスが使いこなせてない気がする