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

配列のclone

CopyUtilsのtoCopyableに配列を渡したらNoSuchMethodExceptionが発生した。

getMethodで怒られてるみたいなんで、とりあえず配列がどんなメソッドを持ってるか確認してみた。

import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws Exception {
        for (Method method : args.getClass().getDeclaredMethods())
            System.out.println(method);
    }
}

↓実行結果

public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

あれ、clone持ってない・・・
とりあえずgetDeclaredMethodsもためしてみたけど、こちらは何にも出てこない。リフレクションを使わずに、

public class Main {
    public static void main(String[] args) throws Exception {
        args.clone();
    }
}

は問題ないから持ってるはずなんだけどなー。
一応言語仕様を確認すると、

The members of an array type are all of the following:

  • The public final field length, which contains the number of components of the array (length may be positive or zero).
  • The public method clone, which overrides the method of the same name in class Object and throws no checked exceptions. The return type of the clone method of an array type T is T.
  • All the members inherited from class Object; the only method of Object that is not inherited is its clone method.
The Java Language Specification, Third Edition - TOC 10.7 Array Members

publicメソッドでclone持ってるって書いてあるじゃん。しかも、オーバーライドしてて、Object clone() throws CloneNotSupportedExceptionじゃなくてT[] clone()だし*1。ってことは、

for (String str : args.clone()) ...

とかいけちゃうんだ。例外処理の必要もなし。いや、問題はそこじゃない。うーん、困った。仕方が無いから、今は配列だけ特別扱いにしてみる。

public final class CopyUtils {

    private CopyUtils() {}
    
    public static <E extends Cloneable> Copyable<E> toCopyable(E obj) {
        try {
            Class<?> clazz = obj.getClass();
            if (clazz.isArray())
                return new CopyableArrayImpl<E>(obj);
            Method clone = clazz.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); }
    }
    
    private static final class CopyableArrayImpl<E extends Cloneable> implements Copyable<E> {
        private final Object[] array;
        CopyableArrayImpl(E obj) {
            this.array = (Object[]) obj;
        }
        
        @Override
        public Object clone() { return array.clone(); }
        
        @SuppressWarnings("unchecked")
        public E copy() { return (E) array.clone(); }
    }
}

渡されたオブジェクトのClassオブジェクトを取得して、そのClassオブジェクトのisArrayメソッドがtrueを返すならCopyableArrayImplクラスのインスタンスを生成して返すことに。CopyableArrayImplクラスは、コンストラクタで受け取ったオブジェクトを配列にキャストして保持しておいて、copyやらcloneやらが呼ばれたときにcloneを呼ぶだけ。
その他、配列のcloneの実装と同じ感じでCopyableのcloneの戻り値をEにして、例外も投げないようにしようかとも思ったけど、すると名前が違うだけで機能が全く同じメソッドができてしまう。ということで、今回は見送り*2
それにしても、配列のclone周りはもうちょっと調べてみよう。




関係ないけど、仕様眺めてたら、こんなことも可能だということが分かった。

String method()[] {
    return new String[] { "aaa", "bbb", "ccc" };
}

何でも、古いバージョンとの互換性のためだとか。絶対使わないな。

*1:covariant return typeって言うんだ

*2:この変更をするなら、copyは消してしまって、名前もCopyableじゃなくてClonableにするとか。・・・Clonableは紛らわしいな。ExCloneableとか。全然Exじゃないけどね