Я делаю некоторые тесты с помощью общих методов, и я хотел бы преобразовать эти два метода ниже (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;
}
}
Но когда я пытаюсь использовать generics, как показано ниже, у меня есть некоторые ошибки:
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 для дженериков, но мне интересно, знает ли кто-нибудь какое-либо решение, используя общий метод, который я не вижу.
Начиная с текущей версии (Java 10), примитивные типы не могут быть представлены с помощью дженериков 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 = Arrays.asList(1, 2, 3);
int[] ints = toPrimitiveArray(list, int[].class);
Обратите внимание, что Array.set
будет выполнять расширяющееся примитивное преобразование, поэтому работает следующее:
List<Integer> list = Arrays.asList(1, 2, 3);
double[] doubles = toPrimitiveArray(list, double[].class);
Но он не будет выполнять сужающее преобразование, поэтому следующее исключает исключение:
List<Integer> list = Arrays.asList(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
of Integer
, Long
и Double
с помощью Stream
API:
List<Integer> list = Arrays.asList(1, 2, 3);
int[] ints = list.stream().mapToInt(Integer::intValue).toArray();
Однако это не общее решение.