配列を分割する(Generics版) ※追記あり

配列を指定サイズで分割したい!
なんてシチュエーションは少ないかもしれませんが、そんな数少ないシチュエーションに先日遭遇しました。


自分が必要としたのは String[] の分割処理だったのですが、せっかくですので、汎用版を作ってみました。

package util;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

public class Util {

    /**
     * 配列を指定サイズで分割します。分割された配列は List (ArrayList) に格納して戻します。
     * <p>
     * 例)
     * <pre>
     * String[] array = new Foo[] { "1", "2", "3", "4", "5", "6", "7" };
     * List&lt;String[]&gt; result = Util.splitArray(array, 3, String.class);
     *
     * ==&gt; [ {"1", "2", "3"}, {"4", "5", "6"}, ("7"} ]
     * </pre>
     *
     * @param <T> 分割元配列の型
     * @param array 分割元となる配列
     * @param size 分割サイズ
     * @param arrayClass 分割後配列の型
     * @return 分割された配列のリスト
     */
    public static <T> List<T[]> splitArray(T[] array, int size, Class<?> arrayClass) {
        List<T[]> list = new ArrayList<T[]>();
        for (int i = 0; i < array.length; i += size) {
            int subArrayLength = (array.length - i > size) ? size : (array.length - i);
            T[] subArray = newArrayInstance(arrayClass, subArrayLength);
            System.arraycopy(array, i, subArray, 0, subArrayLength);
            list.add(subArray);
        }
        return list;
    }

    @SuppressWarnings("unchecked")
    private static <T> T[] newArrayInstance(Class<?> arrayClass, int subArrayLength) {
        return (T[]) Array.newInstance(arrayClass, subArrayLength);
    }
}

T型の配列は new できないので、配列の型情報を渡して Array.newInstance で強引に配列を作っています。
ここを上手く解決できる方法ってあるのかなぁ。

(2009/09/14 1:00 追記) Class#getComponentType() を使用する方法で解決できるようです。詳細は http://blog.smartnetwork.co.jp/staff/node/35 を参照してください。GJ!



おまけ:テストクラス

package util;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.List;

import org.junit.Test;

public class UtilTest {

    @Test
    public void splitArrayのテスト_配列が分割されるパターン() {
        List<String> list = new ArrayList<String>();
        for (int i = 0 ; i < 8; i++) {
            list.add(String.valueOf(i));
        }
        String[] array = list.toArray(new String[] {});


        List<String[]> actualList = Util.splitArray(array, 3, String.class);


        assertThat(actualList.size(), is(3));

        assertThat(actualList.get(0).length, is(3));
        assertThat(actualList.get(0), is(new String[] {"0","1","2"}));

        assertThat(actualList.get(1).length, is(3));
        assertThat(actualList.get(1), is(new String[] {"3","4","5"}));

        assertThat(actualList.get(2).length, is(2));
        assertThat(actualList.get(2), is(new String[] {"6","7"}));
    }

    @Test
    public void splitArrayのテスト_配列が分割されないパターン() {
        List<String> list = new ArrayList<String>();
        for (int i = 0 ; i < 5; i++) {
            list.add(String.valueOf(i));
        }
        String[] array = list.toArray(new String[] {});


        List<String[]> actualList = Util.splitArray(array, 5, String.class);


        assertThat(actualList.size(), is(1));
        assertThat(actualList.get(0), is(new String[] {"0","1","2","3","4"}));
    }

    @Test
    public void splitArrayのテスト_配列が巨大な場合のパフォーマンス確認() {
        List<String> list = new ArrayList<String>();
        for (int i = 0 ; i < 1000000; i++) {
            list.add(String.valueOf(i));
        }
        String[] array = list.toArray(new String[] {});

        final int splitSize = 10000;

        long start = System.nanoTime();
        List<String[]> actualList = Util.splitArray(array, splitSize, String.class);
        long execTime = System.nanoTime() - start;
        System.out.println(execTime / 1000 / 1000 + " msec.");

        assertThat(actualList.size(), is(100));
        for (int i = 0; i < actualList.size(); i++) {
            assertThat(actualList.get(i).length, is(splitSize));
        }
    }
}