为什么 Stream 没有 toList() 方法?



当使用Java 8流时,通常会获取一个列表,从中创建一个流,执行业务并将其转换回。类似于:

 Stream.of(-2,1,2,-5)
        .filter(n -> n > 0)
        .map(n -> n * n)
        .collect(Collectors.toList());

为什么".collect(Collectors.toList())"零件没有捷径/方便的方法?在Stream接口上,有一种将结果转换为数组的方法,称为toArray(),为什么缺少toList()

IMHO,将结果转换为列表比转换为数组更常见。我可以接受,但称之为丑陋是很烦人的。

有什么想法吗?

最近我写了一个名为StreamEx的小型库,它扩展了Java流,并在许多其他功能中提供了这种精确的方法:

StreamEx.of(-2,1,2,-5)
    .filter(n -> n > 0)
    .map(n -> n * n)
    .toList();

还有toSet()、toCollection(Supplier)、joining()、groupingBy()和其他快捷方式。

Java 16引入了Stream.toList():

Stream.of(-2,1,2,-5)
    .filter(n -> n > 0)
    .map(n -> n * n)
    .toList();

新方法与现有的collect(toList())略有不同:它返回一个不可修改的列表。

至于"为什么",我相信评论中有很多争论。然而,我同意你的观点,因为没有toList()方法是很烦人的。toIterable()方法也是如此。

因此,我将向您展示一个技巧,让您无论如何都可以使用这两种方法。幸运的是,Java非常灵活,允许您做各种有趣的事情。大约10年前,我读过这篇文章,其中描述了一个将方法"插入"到任何给定接口的妙招。诀窍在于使用代理来调整没有所需方法的接口。多年来,我发现它具有适配器模式的所有优点,而缺少所有缺点。这就是我所说的大事。

这里有一个示例代码,只是为了展示这个想法:

public class Streams {
    public interface EnhancedStream<T>
        extends Stream<T> {
        List<T> toList();
        Iterable<T> toIterable();
    }
    @SuppressWarnings("unchecked")
    public static <T> EnhancedStream<T> enhance(Stream<T> stream) {
        return (EnhancedStream<T>) Proxy.newProxyInstance(
            EnhancedStream.class.getClassLoader(),
            new Class<?>[] {EnhancedStream.class}, 
            (proxy, method, args) -> {
            if ("toList".equals(method.getName())) {
                return stream.collect(Collectors.toList());
            } else if ("toIterable".equals(method.getName())) {
                return (Iterable<T>) stream::iterator;
            } else {
                // invoke method on the actual stream
                return method.invoke(stream, args);
            }
        });
    }
    public static void main(String[] args) {
        Stream<Integer> stream1 = Stream.of(-2, 1, 2, -5).
            filter(n -> n > 0).map(n -> n * n);
        List<Integer> list = Streams.enhance(stream1).toList();
        System.out.println(list); // [1, 4]
        Stream<Integer> stream2 = Stream.of(-2, 1, 2, -5).
            filter(n -> n > 0).map(n -> n * n);
        Iterable<Integer> iterable = Streams.enhance(stream2).toIterable();
        iterable.forEach(System.out::println); // 1
                                               // 4
    }
}

这个想法是使用EnhancedStream接口,通过定义要添加的方法来扩展Java的Stream接口。然后,动态代理通过将原始Stream方法委派给正在调整的实际流来实现这个扩展接口,而它只是为新方法(Stream中未定义的方法)提供内联实现。

这个代理可以通过一个静态方法获得,该方法透明地执行所有代理操作。

请注意,我并不是说这是一个最终解决方案。相反,它只是一个可以高度改进的示例,即对于Stream的每个返回另一个Stream的方法,您也可以返回该方法的代理。这将允许EnhancedStream被链接(您需要在EnhancedStream接口中重新定义这些方法,以便它们返回EnhancedStream协变返回类型)。此外,缺少适当的异常处理,以及更健壮的代码来决定是否将方法的执行委托给原始流。

这里有一个简单的助手类,可以让这更容易:

public class Li {
    public static <T> List<T> st(final Stream<T> stream) {
        return stream.collect(Collectors.toList());
    }
}

示例用法:

List<String> lowered = Li.st(Stream.of("HELLO", "WORLD").map(String::toLowerCase));

最新更新