How to convert List to Array t[] (for primitive types) using generic-method?

后端 未结 1 491
不思量自难忘°
不思量自难忘° 2021-01-21 04:35

I am doing some tests with generic-methods and I\'d like to transform these two methods below (convertFloatListToArray and convertShortListToArray) in just one (convertListToArr

相关标签:
1条回答
  • 2021-01-21 05:34

    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:

    • We don't know what type of array to create if the list is empty.
    • Doesn't work if there's more than one type of object in the list.
    • Unchecked casting of the result array to 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 Lists 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);
      
    0 讨论(0)
提交回复
热议问题