I am doing some tests with generic-methods and I'd like to transform these two methods below (convertFloatListToArray and convertShortListToArray) in just one (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;
}
}
But when I try to use generics, as below, I have some errors:
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;
}
}
I can understand java limitations about generics, but I wonder if someone know any solution, using generic-method, that I am not seeing.
As of the current version (Java 12), primitive types can't be represented with Java generics. More specifically, we can't provide a primitive type as a type argument. (We can't do e.g. Foo<int>
.) We also can't use type variables as the type in a new
expression, so we can't do new T[n]
to create an array. Therefore, there's no ideal way to do this.
It is possible to do this reasonably using some reflection (java.lang.reflect.Array
), but we need to provide a Class
as an argument. Here's an example of how it might be done:
/**
* 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;
}
Example call:
List<Integer> list = List.of(1, 2, 3);
int[] ints = toPrimitiveArray(list, int[].class);
Note that Array.set
will perform a widening primitive conversion, so the following works:
List<Integer> list = List.of(1, 2, 3);
double[] doubles = toPrimitiveArray(list, double[].class);
But it won't perform a narrowing conversion, so the following throws an exception:
List<Integer> list = List.of(1, 2, 3);
byte[] bytes = toPrimitiveArray(list, byte[].class); // throws
If you wanted, that code could also be used to make duplication easier:
public static int[] toIntArray(List<Integer> list) {
return toPrimitiveArray(list, int[].class);
}
public static double[] toDoubleArray(List<Double> list) {
return toPrimitiveArray(list, double[].class);
}
...
(Having multiple methods like that isn't really generic, though.)
One solution that you'll sometimes see places looks something like this:
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;
}
There are a variety of problems with that, though:
P
, so there's a danger of heap pollution.It's much better to just pass in a Class
as an argument.
Also, while it's possible to just write many overloads which unbox arrays:
public static int[] unbox(Integer[] arr) {...}
public static long[] unbox(Long[] arr) {...}
public static double[] unbox(Double[] arr) {...}
...
Because of the effects of type erasure, it's impossible to write overloads which unbox many different types of List
, as in the following:
public static int[] unbox(List<Integer> list) {...}
public static long[] unbox(List<Long> list) {...}
public static double[] unbox(List<Double> list) {...}
...
That won't compile, because we aren't allowed to have more than one method in the same class with the same name and erasure. The methods would have to have different names.
As a side-note, here are some non-generic solutions:
As of Java 8 we can unbox List
s of Integer
, Long
and Double
using the Stream
API:
List<Long> list = List.of(1L, 2L, 3L);
long[] longs = list.stream().mapToLong(Long::longValue).toArray();
Google Guava has Collection
unboxing methods in their com.google.common.primitives
classes, for example Doubles.toArray
:
List<Double> list = List.of(1.0, 2.0, 3.0);
double[] doubles = Doubles.toArray(list);