開発備忘録

WPF(XAML+C#)の話題を中心に.NET/Android/CIに関するブログです

(Java)コレクションをプリミティブ型の配列に変換する方法

Java でコレクションをプリミティブ型の配列に変換する際に迷ったのでメモ

開発環境

変換方法

まず、やりたいことは以下の通り。

// 仮にList<Integer> を作る。
List<Integer> target = new ArrayList<>();
for (int i=0; i<100000; i++) {
    target.add(i);
}
// ここをどうにかしたい。
int[] result = target;

List などのコレクションは、Integer などの参照型しかジェネリックに指定できません。
その為、プリミティブ型のint 配列などへ変換することができません。
"java collection to array primitive" などで検索しましたが、先頭に引っかかったStackOverflow では以下のようなコメントが有りました。

stackoverflow.com

Unfortunately, I don't believe there really is a better way of doing this due to the nature of Java's handling of primitive types, boxing, arrays and generics. In particular:

・List.toArray won't work because there's no conversion from Integer to int

・You can't use int as a type argument for generics, so it would have to be an int-specific method (or one which used reflection to do nasty trickery).

I believe there are libraries which have autogenerated versions of this kind of method for all the primitive types (i.e. there's a template which is copied for each type). It's ugly, but that's the way it is I'm afraid :(

Even though the Arrays class came out before generics arrived in Java, it would still have to include all the horrible overloads if it were introduced today (assuming you want to use primitive arrays).

そうかー、良い方法は無いのか…
と、ネガティブな結果となりましたが、それでも2018年だし何かしらベターな方法があるのでは?
と思い調査しました。
その結果、Apache Commons Language3 にプリミティブ型配列への変換機能を見つけました。

Apache Commons Language3 を使う
List<Integer> target = new ArrayList<>();
for (int i=0; i<100000; i++) {
    target.add(i);
} 

// org.apache.commons.lang3.ArrayUtils#toPrimitive(Integer[]) メソッドを使用して
// List<Integer> → Integer[] → int[] の順にキャストする。
int[] result = org.apache.commons.lang3.ArrayUtils.toPrimitive(target.toArray(new Integer[0]));

コレクションを使用した場合、一度参照型の配列へ変換する必要があります。
あまり美しくはないですが、使用頻度を考慮すればこれでも良い気がします。

ただ、

target.toArray(new Integer[0])

この部分がものすごく書き心地が悪いのでもう少し頑張りたいところ。

そこで、org.apache.commons.lang3.ArrayUtils#toPrimitive(Integer[]) メソッドの実装を確認したところ、以下のようになっていました。

public static int[] toPrimitive(final Integer[] array) {
    if (array == null) {
        return null;
    } else if (array.length == 0) {
        return EMPTY_INT_ARRAY;
    }
    final int[] result = new int[array.length];
    for (int i = 0; i < array.length; i++) {
        result[i] = array[i].intValue();
    }
    return result;
}

上記のStackOverflow の質問者も似たようなコードを書いていました。
なので、こちらを参考にした変換メソッドを作ってみました。

独自の変換メソッドを使う
List<Integer> target = new ArrayList<>();
for (int i=0; i<100000; i++) {
    target.add(i);
}

final int[] result = new int[target.size()];
for (int i=0; i<result.length; i++) {
    result[i] = target.get(i);
}

ほんとにそのまま持ってきただけです。
やっぱりfor 文を使用すると途端にわかりにくくなるのは否めません。

とはいえ、上記のどちらもUtils のstatic メソッドにしてしまえば使用者が見ることは無いのでどちらでも良い気がします。
どちらでも良いのですが、個人的事情もあり、今回は実行速度の早い方を採用したいと思います。

どちらが速いか?
  • for Apache Commons Language3
List<Integer> target = new ArrayList<>();
for (int i=0; i<100000; i++) {
    target.add(i);
}

long begin = System.currentTimeMillis();

int[] result = org.apache.commons.lang3.ArrayUtils.toPrimitive(target.toArray(new Integer[0]));

long finish = System.currentTimeMillis();
System.out.println((finish - begin) + "ms");
  • for 独自の変換メソッド
List<Integer> target = new ArrayList<>();
for (int i=0; i<100000; i++) {
    target.add(i);
}

long begin = System.currentTimeMillis();

final int[] result = new int[target.size()];
for (int i=0; i<result.length; i++) {
    result[i] = target.get(i);
}

long finish = System.currentTimeMillis();
System.out.println((finish - begin) + "ms");

実行結果は以下の通り。

  • for Apache Commons Language3 : 8ms
  • for 独自の変換メソッド : 1ms

厳密にベンチマークテストできているわけではないですが、明らかに差があります。
ということで、速度を求めた結果、以下のようになりました。

public final class ArrayUtils {
    
    public static int[] toArray(List<Integer> target) {
    
        final int[] result = new int[target.size()];
        for (int i=0; i<result.length; i++) {
            result[i] = target.get(i);
        }
        
        return result;
    }
}

ブログ用にエラー処理などは記述していません。

ちなみにC#だとどうなるか

C# だと、コレクションから配列への変換は以下のようになります。
といってもC# は言語仕様上、コレクションの型にプリミティブ型を使用することができるため、Java と全く同じではないですが。

var target = new List<int>();

for (int i = 0; i < 100000; i++)
{
    target.Add(0);
}

target.ToArray();

実行速度は1/10 ミリ秒 ~ 2/10 ミリ秒程度でした。