为啥新的 Java 8 流在 toArray 调用上返回一个对象数组?

Posted

技术标签:

【中文标题】为啥新的 Java 8 流在 toArray 调用上返回一个对象数组?【英文标题】: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 数组”。 【参考方案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 的问题,但这是我一直在寻找的答案。

以上是关于为啥新的 Java 8 流在 toArray 调用上返回一个对象数组?的主要内容,如果未能解决你的问题,请参考以下文章

为啥转换流在第一次写入后停止写入?

java 怎么把数组转为list

为啥 OrderedQueryable 类的 ToArray 方法分配很多? [关闭]

Java ArrayList toArray避免复制

java中Collection方法里面的Object[] toArray() 和 <T> T[] toArray(T[] a)有啥区别吗?

为啥 ToArray 性能有这样的行为,.Net Core 3.1?