问题 为什么Collection.toArray(T [])不采用E []


toArray 方法(让我们选择实现 java.util.ArrayList)如下:

class ArrayList<E> ....{
    public <T> T[] toArray(T[] a){
        if(a.length < size)
            return (T[]) Arrays.copyof(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if(a.length > size)
            a[size] = null;
        return a;
    }    
}

我想知道我们可以使用 <E> 代替 <T> 在这种情况下 ?喜欢

public E[] toArray(E[] a){
      if(a.length < size)
             return (E[]) Arrays.copyof(elementData, size, a.getClass());
      System.arraycopy(elementData, 0, a, 0, size);
      if(a.length > size)
            a[size] = null;
      return a;
}    

由于ArrayList类iteself已经是通用的 <E>,我们可以使用它而不是新的通用类型 <T> ?


9392
2017-09-10 16:38


起源

你什么意思 我们 可以用 <E> 代替 T  - ?你在问为什么吗? ArrayList<E>.toArray() 是这样定义的?或者你在使用一个实例时问 ArrayList 为什么我们不能自动暗示 E 在里面 toArray() ? - Bhaskar
人们似乎总是在问 toArray 关于 ArrayList,但实际上这个方法 声明 Collection。 - Paul Bellora
是的你是对的让我更新这个问题 - peter


答案:


重点 <T> 是否所需的数组是基类 E。例如,如果 E 是 HashMap 但是所需的阵列是 Map[]。如果 toArray 被锁定了 E 这是不可能的。

由于类型擦除,通用集合/类型中不需要这种类型的东西。但是阵列没有类型擦除,因此阵列的类型非常重要。


5
2017-09-10 16:41



你的第一句话是什么意思? - peter
采取 Integer,它延伸 Number。如果你有 List<Integer> 但需要将其转换为 Number[],返回的类型 toArray 是 T 将它限制为仅返回 Integer[]。当前签名允许它返回任何类型 Integer 可以演员(数字,可比较等)。 - John B
那么使用单独的通用类型T的目的主要是为了防止这种情况? - peter
是的,我相信 - John B
我觉得你的E和T逆转了。但无论如何,声明方法的方式,两个类型变量之间没有约束。所以你可以要求一个数组 任何类型 (不只是超类型)。我想这没关系,因为在运行时检查数组,你的元素可能真的是那种类型。但这意味着编译时检查的次数比您想象的少。 - newacct


我认为 约翰B的答案 很好地介绍了这个想法 - 我只想详细说明一下。

首先,让我们看一下您在问题中提出的方法签名:

public E[] toArray(E[] a)

正如约翰所解释的那样,这种签名不够灵活。如果我想转储 ArrayList<Integer> 变成一个 Number[],这种方法不让我。 Collections API希望允许这种灵活性。

不幸的是,现有的方法不允许对数组类型进行编译时检查,这是您似乎遇到的问题。的文档 toArray,由宣布 Collection,解释说 ArrayStoreException 可能会抛出“如果指定数组的运行时类型不是此集合中每个元素的运行时类型的超类型”。

基于该描述,它 似乎 喜欢以下签名是理想的:

public <T super E> T[] toArray(T[] a)

乍一看,这似乎允许传入和填充任何合法类型的数组 - 但是会在编译时而不是运行时提供类型检查。那么为什么这个签名没有被声明呢?

好吧,这个语法:

 <T super E>

不受语言支持。但是这很容易让你分心这种类型检查无论如何都不会起作用的事实。问题是与参数化类型不同, 数组是协变的。一个 Integer[] 是一个 Number[] 是一个 Object[]。鉴于我们的假设签名 <T super E>如果我打电话的话 toArray 在...上 ArrayList<Number> 并通过了 Integer[],它仍然会编译 - 并且可能在运行时失败,具体取决于列表中的内容。

所以底线是,传入的数组的组件类型的下限不会有任何好处。


11
2017-09-11 04:59



我想你的意思是“如果我打电话的话 toArray 在...上 ArrayList<Integer> 并通过了 Number[]“因为 Number 是超级类型的 Integer。除此之外,我遇到了麻烦。你能提供一个例子吗? ArrayList<Integer> 哪里 toArray 变成一个 Number[] 在运行时失败? - Daniel Trebbien
@DanielTrebbien感谢您的提问。我的意思正是我写的,作为一个例子 似乎 喜欢它不会编译,直到你记得那个 Integer[] 也是一个 Number[] 由于阵列协方差。这是一个假设语法的假设示例 <T super E> 是合法的(事实并非如此)。 - Paul Bellora
@DanielTrebbien至于你要求的例子,考虑一下 Long[] 也是一个 Number[]。所以我可以打电话 toArray 在...上 ArrayList<Integer> 并传入 Long[]  - 它会编译但在运行时失败 ArrayStoreException。 - Paul Bellora
啊哈!我得到你现在说的话。谢谢。 - Daniel Trebbien