问题 在泛型方法中创建通用数组实例


我正在尝试构建一个帮助方法,将两行列表转换为数组转换为一行。我遇到的问题是我不确定如何创建一个T []实例。

我试过了

Array.newInstance(T.class, list.size) 但我不能喂它T.class ..

也试过了 new T[](list.size) 但它不喜欢参数。

public <T> T[] ConvertToArray(List<T> list)
{
   T[] result = ???

   result = list.toArray(result);

   return result;
}

还有其他想法吗?

谢谢


1758
2017-12-02 16:19


起源

感谢您修复我的ToArray S.P ..你能说我是一个天生的C#开发者吗? :d - Shane Courtrille


答案:


你不能混合泛型和类似的数组。泛型具有编译时检查,数组具有运行时检查,并且这些方法大多不兼容。起初我建议:

@SuppressWarnings("unchecked")
public <T> T[] ConvertToArray(List<T> list)
{      
   Object[] result = new Object[list.size()];
   result = list.toArray(result);
   return (T[])result;
}

这是隐秘的错误,因为至少有一个其他人认为它会起作用!但是,当您运行它时会出现不兼容的类型错误,因为您无法将Object []强制转换为Integer []。为什么我们不能得到T.class并创建一个正确类型的数组?或者做 new T[]

泛型使用 类型擦除 保持向后兼容性。它们在编译时检查,但从运行时剥离,因此字节码与预泛化JVM兼容。这意味着您无法在运行时获得泛型变量的类知识!

所以,虽然你可以保证 T[] result 将是Integer []类型的提前,代码 list.toArray(result); (要么 new T[], 要么 Array.newInstance(T.class, list.size());)只会在运行时发生,它不知道T是什么!

这是一个版本  工作,作为阅读该讲座的奖励:

public static <T> T[] convertToArray(List<?> list, Class<T> c) {
    @SuppressWarnings("unchecked")
    T[] result = (T[]) Array.newInstance(c, list.size());
    result = list.toArray(result);
    return (T[]) result;
}

请注意,我们有第二个参数在运行时(以及通过泛型编译时)提供类。你会像这样使用它:

Integer[] arrayOfIntegers = convertToArray(listOfIntegers, Integer.class);

这值得麻烦吗?我们仍然需要抑制警告,所以它绝对安全吗?

我的回答是肯定的。生成的警告只是来自编译器的“我不确定”。通过单步执行,我们可以确认该转换将始终成功 - 即使您将错误的类作为第二个参数,也会抛出编译时警告。

这样做的主要优点是我们已将警告集中到一个地方。我们只需要证明这一个地方是正确的,我们知道代码将永远成功。引用Java文档:

该语言旨在保证如果使用javac -source 1.5编译整个应用程序而没有未经检查的警告,则它是类型安全的[1]

所以现在不是在你的代码中都有这些警告,它只是在一个地方,你可以使用它而不必担心 - 通过使用它可以大大降低你犯错误的风险。

你可能也想看看 这个答案 这更深入地解释了这个问题,并且 这个答案 写这篇文章时,这是我的床单。以及 已经引用了Java文档,我用的另一个方便的参考 这篇博文 作者:Sun Microsystems前高级工程师Neal Gafter,以及1.4和5.0语言功能的联合设计师。

当然,感谢ShaneC正确地指出我的第一个答案在运行时失败了!


13
2017-12-02 16:27



这个的问题是,如果你调用它Integer [] results = convertToArray(listOfIntegers)你将得到一个错误,你不能将java.lang.Object强制转换为java.lang.Integer - Shane Courtrille
ShaneC是正确的,这不起作用。对不起,我写这篇文章的时候没有Java编译器! - ZoFreX
固定!谢谢谢恩! - ZoFreX
谢谢你这么棒的答案! - Shane Courtrille


如果你不能传入 T.class那你基本上搞砸了。类型擦除意味着你根本不知道它的类型 T 在执行时。

当然还有其他指定类型的方法,例如 超级型代币  - 但我的猜测是,如果你不能通过 T.class,您也无法传递类型令牌。如果可以,那就太好了:)


2
2017-12-02 16:24



这对我来说似乎有点矫枉过正.. 叹 - Shane Courtrille
@Ed:固定,谢谢。 @ShaneC:类型擦除很糟糕,基本上......特别是如果你已经习惯了.NET非常不同的泛型实现。 - Jon Skeet
@Jon Skeet给了我一个C#的跨平台/开源版本,没有微软的任何地方,我会马上跳过它 - Sean Patrick Floyd
@ S.P.Floyd:你看过Mono吗? - Jon Skeet
@Jon Skeet肯定,几年前(当时看起来很粗糙)。应该可以再次检查一下 - Sean Patrick Floyd


问题是因为类型擦除,List在运行时不知道它的组件类型,而组件类型是创建数组所需的类型。

因此,您可以在API中找到两个选项:

我能想到的唯一另一种可能性是一个巨大的麻烦:

  • 迭代列表中的所有项目并找到“最大公约数”,这是所有项目扩展或实现的最具体的类或接口。
  • 创建该类型的数组

但这将比你的两行更多(并且它也可能导致客户端代码做出无效的假设)。


1
2017-12-02 16:36



请注意我的方式不起作用,所以只有一个选项! - ZoFreX
更新:修正了我的方式,但它现在与Jon Skeet的答案相同。仍然只有一个选择。 - ZoFreX
这种方法有一个问题 List<Animal> 恰好只包含 Cat 实例意味着你返回一个 Cat[] 代替 Animal[],当它试图坚持一个时,导致其他一些代码失败 Dog 进入结果数组。 - Gabe
我知道,这正是我在上一句话中的意思。 - Sean Patrick Floyd


这真的需要改成单行吗?对于任何具体类,您可以在一行中执行List to Array转换:

MyClass[] result = list.toArray(new MyClass[0]);

当然,这不适用于类中的泛型参数。

见约书亚布洛赫 有效的Java第二版,第25项:首选列表到阵列(第119-123页)。这是其中的一部分 样本章PDF


0
2017-12-02 18:35



你应该做 MyClass[] result = list.toArray(new MyClass[list.size()]);,然后传入的数组可以重复使用。 - Sean Patrick Floyd


这是我能看到的最接近你想要的东西。这使得您在编写代码时知道类的大假设以及列表中的所有对象都是同一个类,即没有子类。我确信这可以更复杂,以减轻没有子类的假设,但我不知道如何在Java中你可以绕过在编码时知道类的假设。

package play1;

import java.util.*;

public class Play
{
  public static void main (String args[])
  {
    List<String> list=new ArrayList<String>();
    list.add("Hello");
    list.add("Shalom");
    list.add("Godspidanya");

    ArrayTool<String> arrayTool=new ArrayTool<String>();
    String[] array=arrayTool.arrayify(list);

    for (int x=0;x<array.length;++x)
    {
      System.out.println(array[x]);
    }
  }
}
class ArrayTool<T>
{
  public T[] arrayify(List<T> list)
  {
    Class clazz=list.get(0).getClass();
    T[] a=(T[]) Array.newInstance(clazz, list.size());
    return list.toArray(a);
  }
}

-1
2017-12-02 18:08