Вопрос по generic-method, arrays, collections, generics, java – Как преобразовать List <T> в Array t [] (для примитивных типов), используя generic-метод?

3

Я делаю некоторые тесты с помощью generic-методов, и я хотел бы преобразовать эти два метода ниже (convertFloatListToArray и convertShortListToArray) в один (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 для дженериков, но мне интересно, знает ли кто-нибудь какое-либо решение, использующее дженерик-метод, которого я не вижу.

@ user2864740 Но short [] или float [] не являются примитивным типом. Paulo
возможный дубликатПреобразовать общий список в массив Adam Arold
@PauloLeiteshort[] это «тип массива». И, будучи не T [] / Object [], эта специальная примитивная обработка все еще актуальна. user2864740
@PauloLeite: краткий ответ, что выне могу сделать это, не переходя вClass<E> к методу. Louis Wasserman

Ваш Ответ

1   ответ
6

примитивные типы не могут быть представлены с помощью обобщений Java. В частности, мы не можем предоставить примитивный тип в качестве аргумента типа. (Мы не можем сделать, например,Foo<int>.) Мы также не можем использовать переменные типа в качестве типа вnew выражение, поэтому мы не можем сделатьnew T[n] создать массив. Поэтому нет идеального способа сделать это.

Этоявляется можно сделать это разумно, используя некоторое отражение (java.lang.reflect.Array), но нам нужно предоставить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);
}
...

(Наличие нескольких таких методов на самом деле необщий, хоть.)

Одно из решений, которое вы иногда видите, выглядит примерно так:

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;
}

Однако с этим есть множество проблем:

Мы не знаем, какой тип массива создавать, если список пуст.Не работает, если в списке более одного типа объектов.Не проверено литье результирующего массива вPтак что есть опасностьзагрязнение кучи.

Гораздо лучше просто пройти вClass в качестве аргумента.

Кроме того, хотя можно просто написать много перегрузок, которые распаковывают массивы:

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

Из-за последствийстирание типаневозможно записать перегрузки, которые распаковывают много разных типовList, как в следующем:

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

Это не скомпилируется, потому что нам не разрешено иметь более одного метода в одном классе с одним и тем же именем и стиранием. Методы должны иметь разные имена.

Как примечание, вот некоторые неуниверсальные решения:

Начиная с Java 8 мы можем распаковатьListсInteger, Long а такжеDouble с использованиемStream API:

List<Long> list = List.of(1L, 2L, 3L);
long[] longs = list.stream().mapToLong(Long::longValue).toArray();

Google Guava имеетCollection методы распаковки в ихcom.google.common.primitives классы, напримерDoubles.toArray:

List<Double> list = List.of(1.0, 2.0, 3.0);
double[] doubles = Doubles.toArray(list);

Похожие вопросы