配列の動的生成

後輩がはまっていたので。でも確かに面白い問題だった。
次のプログラムは、第一引数の配列のうち第二引数に含まれないものを出力する差分取得プログラムで、期待値は「A」「C」と表示されること。しかし実行するとmain文の1行目でString[]にキャストするところでClassCastExceptionが発生する。理由が分かるだろうか。

public static void main(String[] args) throws Exception {
    String[] difference = (String[])getDifference(new String[]{"A", "B", "C"}, new String[]{"B", "D"});
    for( String str : difference ) {
        System.out.println(str);
    }
}

public static Object[] getDifference(Object[] o1, Object[] o2) {
    List<Object> result = new ArrayList<Object>();

    for (Object obj : o1) {
        if (!ArrayUtils.contains(o2, obj)) result.add(obj);
    }

    return result.toArray(new Object[result.size()]);
}

getDifferenceメソッドの戻り値がObject型配列なのにString型配列にキャストしているから、じゃないですよ。以下はtrueですからね。

    new String[]{"A", "B", "C"} instanceof Object[]

落ち着いて考えれば簡単な問題。
途中でString型配列からObject型配列にすり替わっているのが原因。getDifferenceメソッドの最後のreturnのところですね。ここをObject型配列ではなく、今回の場合はString型配列にしないといけない。もちろん

    return result.toArray(new String[result.size()]);

とすればこのmain文は動くけれど、Integer型配列を2つ渡された場合などはやっぱりClassCastExceptionが発生する。つまり、渡された配列型に応じた配列型を返さなければならない。
さぁどうするか。
自分でも一瞬できないのかな、と思いましたが、配列を動的に作るという点でひらめきました。というわけでgetDifferenceメソッドを以下のようにすればできました♪

public static Object[] getDifference(Object[] o1, Object[] o2) {
    List<Object> result = new ArrayList<Object>();

    for (Object obj : o1) {
	if (!ArrayUtils.contains(o2, obj)) result.add(obj);
    }
		
    Object ary = Array.newInstance(o1.getClass().getComponentType(), result.size());
    for (int i = 0; i < result.size(); i++) {
        Array.set(ary, i, result.get(i));
    }

    return (Object[]) ary;
}

補足

  • ArrayUtilsはcommons-langのものです。使用するにはダウンロードしてjarをクラスパスに追加する必要があります。
  • nullチェックとかはプログラムが長くなるのであえて何もやっていません。
  • そもそもこの問題は配列で頑張るからややこしいんであって、Listの差分を得るようなプログラムにすればすんなりいきます。あまり実用的ではないですが、トレーニング問題としては3年目くらいの人向けにいいかもしれません。