【问题标题】:Why do the new Java 8 streams return an Object Array on toArray calls?为什么新的 Java 8 流在 toArray 调用上返回一个对象数组?
【发布时间】:2014-12-27 02:35:43
【问题描述】:

当我从事一个涉及 Java 8 新流的项目时,我注意到当我在流上调用 Stream#toArray() 时,它返回一个 Object[] 而不是 T[]。尽管我很惊讶,但我开始深入研究 Java 8 的源代码,但找不到任何原因他们没有将 Object[] toArray(); 实现为 T[] toArray();。这背后有什么原因吗,还是只是(不)一致性?

编辑 1: 我注意到很多人在回答中说这是不可能的,但是这段代码sn-p编译并返回了预期的结果?

import java.util.Arrays;

public class Test<R> {

    private Object[] items;

    public Test(R[] items) {
        this.items = items;
    }

    public R[] toArray() {
        return (R[]) items;
    }

    public static void main(String[] args) {
        Test<Integer> integerTest = new Test<>(new Integer[]{
            1, 2, 3, 4
        });

        System.out.println(Arrays.toString(integerTest.toArray()));
    }

}

【问题讨论】:

  • 它怎么知道R?如果您希望返回特定类型,请将生成器传递给该方法。例如对于字符串数组,请执行以下操作:streamString.toArray(String[]::new)
  • 您不知道应该转换为哪种类型,因为在运行时 Java 不知道实际类型,因为 类型擦除(就像它在@Susei 的回答)
  • Object[] 不一定是R[]。该演员在运行时肯定会失败。
  • 对象没有扩展R,所以这会抛出一个RuntimeException
  • R[] 的演员表应该是你的警告信号。这是一个 unchecked 强制转换,编译器会告诉你(但你可能只是忽略了这一点)。 items[] 不是 R[] 的数组,所以编译器说它不能验证这个转换是完全正确的——它不能阻止客户端转换返回的数组到Object[],然后将非 R 值放入其中,这将颠覆假设的类型安全性,即“这是一个 R 数组”。

标签: java java-8


【解决方案1】:

这与List#toArray() 的问题相同。类型擦除使我们无法知道应该返回的数组类型。考虑以下

class Custom<T> {
    private T t;
    public Custom (T t) {this.t = t;}
    public T[] toArray() {return (T[]) new Object[] {t};} // if not Object[], what type?
}

Custom<String> custom = new Custom("hey");
String[] arr = custom.toArray(); // fails

Object[]不是String[],因此无论演员阵容如何,都不能分配给一个Object[]。同样的想法适用于StreamList。使用重载的toArray(..) 方法。

【讨论】:

  • 看看我的编辑。你能解释一下为什么会这样吗?
  • 你的代码没问题,因为items 实际上是一个Integer[],而不是一个被转换成别的东西的Object[]
  • @MartijnRiemers 在您的示例中,您将返回已创建的数组(您知道其类型)。 Stream(或List)不能保证由数组支持。它可以通过任何其他方式构建。这意味着必须创建一个 new 数组,但我们不知道是哪种类型。
  • @MartijnRiemers ArrayList 的底层数组的真实(运行时)类型是Object[]
  • @MartijnRiemers 他们决定使用类型擦除来实现泛型。关于这是一个好的选择还是一个坏的选择,网上有很多讨论。你可能想看看他们。当R 是类型变量时,目前无法执行R.class 之类的操作。
【解决方案2】:

关于toArray()返回Object[]的原因:是因为类型擦除。泛型类型在运行时会丢失它们的类型参数,因此Stream&lt;Integer&gt;Stream&lt;String&gt;Stream 成为相同的类型。因此无法确定要创建的数组的组件类型。其实可以通过反射分析数组元素的类型,然后尝试找到它们的least upper bound,但这太复杂和太慢了。

有一种方法可以通过使用重载的toArray(IntFunction&lt;A[]&gt; generator) 方法来获取R[] 数组。此方法使调用者有机会选择数组的类型。有关代码示例,请参阅此 SO 问题:How to Convert a Java 8 Stream to an Array?

【讨论】:

    【解决方案3】:

    试试:

    String[] = IntStream.range(0, 10).mapToObj(Object::toString).toArray(String[]::new);
    

    无参数的 toArray() 方法只会返回一个 Object[],但如果你传递一个数组工厂(可以方便地表示为数组构造函数引用),你可以获得任何你喜欢的(兼容的)类型。

    【讨论】:

    • 虽然这并不能直接解决 OP 的问题,但这是我一直在寻找的答案。