【问题标题】:How to convert List<T> to Array t[] (for primitive types) using generic-method?如何使用泛型方法将 List<T> 转换为 Array t[](对于原始类型)?
【发布时间】:2014-09-28 17:46:49
【问题描述】:

我正在使用泛型方法进行一些测试,我想将以下两种方法(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 对泛型的限制,但我想知道是否有人知道我没有看到的使用泛型方法的任何解决方案。

【问题讨论】:

  • 简短回答:Java 原始类型在 Object 类型系统下统一/统一。
  • 这确实 not 似乎是该问题的一个很好的重复。 OP 的代码演示了如何从相应的 List&lt;Float&gt;List&lt;Short&gt; 列表中获取 float[]short[] 数组。这与List&lt;Float&gt; -> Float[] 不同。 (而且我不确定为什么会有反对票;例如,我希望能够在 C#/.NET 中做同样多的事情。这值得讨论 Java 泛型实现,或者至少关闭相关的重复。)
  • @PauloLeite:简短的回答是,如果不将Class&lt;E&gt; 传递给方法,您无法这样做。
  • @user2864740 但是 short[] 或 float[] 不是原始类型。

标签: java arrays generics collections generic-method


【解决方案1】:

从当前版本 (Java 12) 开始,原始类型无法用 Java 泛型表示。更具体地说,我们不能提供原始类型作为类型参数。 (我们不能这样做,例如Foo&lt;int&gt;。)我们也不能使用类型变量作为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;
}

不过,这会带来很多问题:

  • 如果列表为空,我们不知道要创建什么类型的数组。
  • 如果列表中有不止一种类型的对象,则不起作用。
  • 结果数组的Unchecked castingP,所以有heap pollution 的危险。

最好只传入 Class 作为参数。


另外,虽然可以编写许多重载来拆箱数组:

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

由于type erasure 的影响,不可能编写将许多不同类型的List 拆箱的重载,如下所示:

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

这不会编译,因为我们不允许在同一个类中拥有多个具有相同名称和擦除的方法。这些方法必须有不同的名称。


附带说明,这里有一些非通用解决方案:

  • 从 Java 8 开始,我们可以使用 Stream API 将 Lists 的 IntegerLongDouble 拆箱:

    List<Long> list = List.of(1L, 2L, 3L);
    long[] longs = list.stream().mapToLong(Long::longValue).toArray();
    
  • Google Guava 在其com.google.common.primitives 类中有Collection 拆箱方法,例如Doubles.toArray

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-19
    • 2017-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多