3

私はジェネリック メソッドを使用していくつかのテストを行っています。以下の 2 つのメソッド (convertFloatListToArray と convertShortListToArray) を 1 つのメソッド (convertListToArray) に変換したいと思います。

public class Helper{
    public static float[] convertFloatListToArray(List<Float> list){
        float[] array = new float[list.size()];

        for(int i = 0; i<list.size(); i++){
            array[i] = list.get(i);
        }

        return array;
    }

    public static short[] convertShortListToArray(List<Short> list){
        short[] array = new short[list.size()];

        for(int i = 0; i<list.size(); i++){
            array[i] = list.get(i);
        }

        return array;
    }
}

しかし、以下のようにジェネリックを使用しようとすると、いくつかのエラーが発生します。

public class Helper{
    public static <T, E> T convertListToArray(List<E> list){
        T array = new T[list.size()];

        for(int i = 0; i<list.size(); i++){
            array[i] = list.get(i);
        }

        return array;
    }
}

ジェネリックに関するJavaの制限を理解できますが、ジェネリックメソッドを使用して、私が見ていない解決策を誰かが知っているのではないかと思います。

4

1 に答える 1

9

現在のバージョン (Java 12) では、プリミティブ型を Java ジェネリックで表すことはできません。より具体的には、プリミティブ型を型引数として提供することはできません。(たとえば はできませんFoo<int>。) また、型変数を式の型として使用できないため、配列を作成するnewこともできません。new T[n]したがって、これを行う理想的な方法はありません。

リフレクション ( ) を使用して合理的にこれを行うこと可能ですが、引数としてjava.lang.reflect.Arraya を指定する必要があります。Classこれがどのように行われるかの例を次に示します。

/**
 * Unboxes a List in to a primitive array.
 *
 * @param  list      the List to convert to a primitive array
 * @param  arrayType the primitive array type to convert to
 * @param  <P>       the primitive array type to convert to
 * @return an array of P with the elements of the specified List
 * @throws NullPointerException
 *         if either of the arguments are null, or if any of the elements
 *         of the List are null
 * @throws IllegalArgumentException
 *         if the specified Class does not represent an array type, if
 *         the component type of the specified Class is not a primitive
 *         type, or if the elements of the specified List can not be
 *         stored in an array of type P
 */
public static <P> P toPrimitiveArray(List<?> list, Class<P> arrayType) {
    if (!arrayType.isArray()) {
        throw new IllegalArgumentException(arrayType.toString());
    }
    Class<?> primitiveType = arrayType.getComponentType();
    if (!primitiveType.isPrimitive()) {
        throw new IllegalArgumentException(primitiveType.toString());
    }

    P array = arrayType.cast(Array.newInstance(primitiveType, list.size()));

    for (int i = 0; i < list.size(); i++) {
        Array.set(array, i, list.get(i));
    }

    return array;
}

呼び出しの例:

List<Integer> list = List.of(1, 2, 3);
int[] ints = toPrimitiveArray(list, int[].class);

Array.set拡張プリミティブ変換を実行することに注意してください。したがって、次のように機能します。

List<Integer> list = List.of(1, 2, 3);
double[] doubles = toPrimitiveArray(list, double[].class);

ただし、縮小変換は実行されないため、次の例では例外がスローされます。

List<Integer> list = List.of(1, 2, 3);
byte[] bytes = toPrimitiveArray(list, byte[].class); // throws

必要に応じて、そのコードを使用して複製を簡単にすることもできます。

public static int[] toIntArray(List<Integer> list) {
    return toPrimitiveArray(list, int[].class);
}
public static double[] toDoubleArray(List<Double> list) {
    return toPrimitiveArray(list, double[].class);
}
...

(ただし、そのような複数のメソッドを持つことは、実際には一般的ではありません。)


ときどき目にする解決策の 1 つは、次のようなものです。

public static <P> P toPrimitiveArray(List<?> list) {
    Object obj0 = list.get(0);
    Class<?> type;
    // "unbox" the Class of obj0
    if (obj0 instanceof Integer)
        type = int.class;
    else if (obj0 instanceof Double)
        type = double.class;
    else if (...)
        type = ...;
    else
        throw new IllegalArgumentException();

    Object array = Array.newInstance(type, list.size());

    for (int i = 0; i < list.size(); i++) {
        Array.set(array, i, list.get(i));
    }

    return (P) array;
}

ただし、それにはさまざまな問題があります。

Class引数としてa を渡すだけの方がはるかに優れています。


また、配列をボックス化解除する多くのオーバーロードを記述することも可能ですが:

public static int[]    unbox(Integer[] arr) {...}
public static long[]   unbox(Long[]    arr) {...}
public static double[] unbox(Double[]  arr) {...}
...

タイプeraserの影響によりList、次のように、さまざまなタイプの をボックス化解除するオーバーロードを記述することはできません。

public static int[]    unbox(List<Integer> list) {...}
public static long[]   unbox(List<Long>    list) {...}
public static double[] unbox(List<Double>  list) {...}
...

同じ名前と消去を持つ同じクラスに複数のメソッドを持つことは許可されていないため、これはコンパイルされません。メソッドには異なる名前を付ける必要があります。


補足として、一般的ではない解決策を次に示します。

  • Java 8 の時点で、APIを使用してListの をボックス化解除できます。IntegerLongDoubleStream

    List<Long> list = List.of(1L, 2L, 3L);
    long[] longs = list.stream().mapToLong(Long::longValue).toArray();
    
  • Google Guavaのクラスには、次のようなボックス化Collection解除メソッドがあります。com.google.common.primitivesDoubles.toArray

    List<Double> list = List.of(1.0, 2.0, 3.0);
    double[] doubles = Doubles.toArray(list);
    
于 2014-08-05T23:11:30.913 に答える