【问题标题】:How to properly return generic array in Java generic method?如何在 Java 泛型方法中正确返回泛型数组?
【发布时间】:2016-01-22 05:18:21
【问题描述】:

我有下面的通用方法返回一个通用数组:

public static <T> T[] genericMethod1(List<T> input) {
    T[] res = (T[]) new Object[input.size()];

    int i = 0;
    for (T t : input) {
        res[i] = t;
        i++;
    }
    return res;
}

public static <T> T genericMethod2(List<T> input) {
    return input.get(0);
}

但后来当我尝试使用以下方法获取结果数组时:

LinkedList<Integer> list = new LinkedList<Integer>();
list.addFirst(1);
list.addFirst(1);

Integer[] i = (Integer[]) genericMethod1(list);  // 1) Runtime error
Integer j = genericMethod2(list);        // 2) works

对于情况 1,我总是在运行时出错:

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;

谁能解释为什么以及如何正确返回泛型数组?谢谢。

以下是我的理解,如有错误请指正。

正如 Tim 提到的,类型擦除发生在编译时,所以在字节码中,每个 T 对象只是类型 Object,同时,编译器会“正确地”添加从 Object 到 T 的类型转换。

假设 T 是一个 Integer,其中 T 被声明,它是 Object。对于它被引用的地方,它是(隐式)类型转换为 T。

除非声明了 T[] 数组,它是 Object[],并且在引用数组的地方,它仍然是 Object[]。不会发生对 T[] 的隐式强制转换。

【问题讨论】:

  • 泛型在运行时不保留类型。
  • 这里总结一下,类型擦除删除了类型,所以如果要返回特定类型的泛型数组,则需要传入该类型。

标签: java generics


【解决方案1】:

您所看到的解释是由于一种称为类型擦除的东西。这是您的genericMethod() 在编译器执行类型擦除之后的样子:

public static Object[] genericMethod(List input) {
    Object[] res = new Object[input.size()];

    int i = 0;
    for (Object t : input) {
        res[i] = t;
        i++;
    }
    return res;
}

换句话说,这个方法将返回一个Object 类型的数组。无法将Object[] 转换为Integer[],因为它们不是同一类型。如果您希望您的方法能够动态返回您想要的类型,那么您可以使用Array.newInstance()。这还需要传入您想要的数组类型作为输入参数:

public static <T> T[] genericMethod(Class<T> clazz, List<T> input) {
    @SuppressWarnings("unchecked")
    T[] res = (T[]) Array.newInstance(clazz, input.size());

    int i = 0;
    for (T t : input) {
        res[i] = t;
        i++;
    }
    return res;
}

现在您的代码 sn-p 将正常运行:

LinkedList<Integer> list = new LinkedList<Integer>();    
Integer[] i = genericMethod(Integer.class, list);

更新:

您的第二种方法 genericMethod2() 在类型擦除后将如下所示:

public static Object genericMethod2(List input) {
    return input.get(0);
}

它将返回输入列表的第一个元素,转换为Object。这是您对该方法的使用:

Integer j = genericMethod2(list);

编译器将尝试将输出从genericMethod2() 转换为Integer

Integer j = (Integer)genericMethod2(list);

这个转换是合法的,因为每个Integer 也是一个Object,而且它在这里成功是因为你传入了一个Integer 的集合。第二种方法与您为我们强调的第一种方法不同。

【讨论】:

  • @Tom Biegeleisen 感谢您的评论。但我猜另一个原因是类型擦除和隐式类型转换不适合数组类型。请查看我修改后的问题。
  • 好的,我会更新我的答案。顺便说一句,汤姆·比格莱森是我的兄弟。
  • 蒂姆和汤姆。听起来像电影:-)
【解决方案2】:

调用该方法时,genericMethod 您假设它返回整数数组,这是不正确的。它实际上在运行时返回 Object 类型的数组。

    List<Integer> input = new ArrayList<Integer>();
    input.add(1);
    Object[] output = genericMethod(input);
    for(Object obj : output){
        System.out.println("Value= "+ (Integer)obj);
    }

所以我们需要转换数组的各个内容。

一个通用的指导方针是,我们不应该在 Java 中混合使用 ARRAY 和 GENERICS。

更新:

来自Effective Java的参考:

总之,数组和泛型有非常不同的类型规则。数组是协变和具体化的;泛型是不变的和被擦除的。因此,数组提供运行时类型安全,但不提供编译时类型安全,反之亦然。一般来说,数组和泛型不能很好地混合。如果您发现自己混合使用它们并收到编译时错误或警告,您的第一反应应该是用列表替换数组。

【讨论】:

    【解决方案3】:

    另一种方法是像java.util.ArrayList.toArray(T[]) 那样做。 你将 Array 的类型传递给那个 Method,如果它足够大,它将被重用,否则会生成一个新的 Array。

    例子:

        List<Integer> intList = new ArrayList<>();
        intList.add(Integer.valueOf(1));
        intList.add(Integer.valueOf(2));
        intList.add(Integer.valueOf(3));
        Integer[] array = intList.toArray(new Integer[] {});
        System.out.println(Arrays.toString(array));//Will Print [1, 2, 3]
    

    ArrayList.toArray(T[]) 的实现参见here

    【讨论】:

      猜你喜欢
      • 2021-02-14
      • 1970-01-01
      • 2016-12-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多