当前位置: 首页 > 知识库问答 >
问题:

为什么Java没有中间合并操作?

陆城
2023-03-14

注意:我不一定要寻找下面描述的具体示例问题的解决方案。我真的很感兴趣,为什么这在java 8中是不可能的。

Java流是懒惰的。最后,他们有一个单一的终端操作<我的解释是,这个终端操作将通过流提取所有值。没有任何中间操作可以做到这一点。为什么没有中间操作通过流拉入任意数量的元素?类似这样:

stream
    .mapMultiple(this::consumeMultipleElements) // or groupAndMap or combine or intermediateCollect or reverseFlatMap
    .collect(Collectors.toList());

当下游操作尝试推进流一次时,中间操作可能尝试多次(或根本不推进)上游。

我会看到几个用例:
(这些只是示例。所以您可以看到处理这些用例当然是可能的,但它“不是流式方式”,并且这些解决方案缺乏Streams所具有的理想的惰性属性。)

>

  • 将多个元素组合成一个新元素,然后传递到流的其余部分。(例如,配对<代码>(1,2,3,4,5,6)➔ ((1,2)、(3,4)、(5,6)))

    // Something like this,
    // without needing to consume the entire stream upfront,
    // and also more generic. (The combiner should decide for itself how many elements to consume/combine per resulting element. Maybe the combiner is a Consumer<Iterator<E>> or a Consumer<Supplier<E>>)
    public <E, R> Stream<R> combine(Stream<E> stream, BiFunction<E, E, R> combiner) {
        List<E> completeList = stream.collect(toList());
        return IntStream.range(0, completeList.size() / 2)
            .mapToObj(i -> combiner.apply(
                    completeList.get(2 * i),
                    completeList.get(2 * i + 1)));
    }
    

    确定流是否为空(将流映射到可选的非空流)

    // Something like this, without needing to consume the entire stream
    public <E> Optional<Stream<E>> toNonEmptyStream(Stream<E> stream) {
        List<E> elements = stream.collect(toList());
        return elements.isEmpty()
            ? Optional.empty()
            : Optional.of(elements.stream());
    }
    

    具有不终止流的惰性迭代器(允许基于更复杂的逻辑跳过元素,然后只跳过(长n)。

    Iterator<E> iterator = stream.iterator();
    // Allow this without throwing a "java.lang.IllegalStateException: stream has already been operated upon or closed"
    stream.collect(toList());
    

    当他们设计流及其周围的一切时,他们是忘记了这些用例还是明确地忽略了这一点<我理解,在处理并行流时,这些可能会产生意外的结果,但在我看来,这是一种可以记录的风险。

  • 共有2个答案

    公孙宇
    2023-03-14

    并非每个终端操作都会“通过流提取所有值”。终端操作iterator()spliterator()不会立即获取所有值并允许执行延迟处理,包括再次构建新的Stream。对于后者,强烈建议使用spliterator(),因为这允许将更多元信息传递给新流,也意味着减少对象的包装。

    E、 g.您的第二个示例可以实现为

    public static <T> Stream<T> replaceWhenEmpty(Stream<T> s, Supplier<Stream<T>> fallBack) {
        boolean parallel = s.isParallel();
        Spliterator<T> sp = s.spliterator();
        Stream.Builder<T> firstElement;
        if(sp.getExactSizeIfKnown()==0 || !sp.tryAdvance(firstElement=Stream.builder())) {
            s.close();
            return fallBack.get();
        }
        return Stream.concat(firstElement.build(), StreamSupport.stream(sp, parallel))
                     .onClose(s::close);
    }
    

    对于你的一般性问题,我不知道这些示例的一般抽象应该是什么样子,除了已经存在的spliterator()方法。正如文件所说

    然而,如果提供的流操作不提供所需的功能,则基流。迭代器()和BaseStream。spliterator()操作可用于执行受控遍历。

    和光启
    2023-03-14

    您想要的所有操作实际上都可以在流API中实现,但不是现成的。

    将多个元素组合成一对元素-您需要一个自定义的拆分器。塔吉尔·瓦列夫正在做这件事。他拥有一个名为StreamEx的绝对强大的库,它可以做许多其他现成不支持的有用的事情。

    我不理解你的第二个例子,但我打赌它也是可行的。

    跳过到更复杂的操作是在java-9中通过以谓词为输入的dropAndtakANG

    请注意,当您说中间操作都不能做到这一点时,这是不准确的-有排序的和不同的。否则他们无法工作。还有类似的flatMap,但这更像是一个bug。

    还有一件事是,并行流的中间操作没有定义的顺序,因此这种有状态的中间操作将具有并行流的未知项。另一方面,你总是可以选择滥用以下内容:

    List<Something> list = Collections.synchronizedList()
    .map(x -> {
         list.add(x);
         // your mapping
     })
    

    如果我是你,我不会那么做,如果我真的需要的话,但以防万一。。。

     类似资料:
    • 问题内容: 我知道每次键入字符串文字时,字符串池中都会引用相同的String对象。 但是,为什么String API不包含,所以我可以使用引用? 至少,这将节省编译时间,因为编译器将知道引用现有的String,而不必检查是否已创建它以进行重用,对吗?我个人认为,字符串文字(尤其是很小的文字)在许多情况下是一种“代码异味”。 那么是否没有String.Empty背后的宏伟设计原因,还是语言创建者根本

    • 问题内容: 在Java中,有和接口。两者都属于Java的标准框架,并提供了一种访问元素的分类方法。 但是,据我了解没有。你可以用来对列表进行排序。 知道为什么要这样设计吗? 问题答案: 列表迭代器首先确保你以列表的内部顺序(也称为插入顺序)获取列表的元素。更具体地说,它是按照插入元素的顺序或操作列表的方式进行的。排序可以看作是对数据结构的一种操作,有几种方法可以对列表进行排序。 我将按照自己的见解

    • 我运行了一个简单的wordcount MapReduce示例,在组合器输出中添加一个小变化的组合器,组合器的输出不被Reducer合并。场景如下 context.write(t,new IntWritable(1));//添加了我自己的输出 我运行了一个简单的wordcount MapReduce示例,在组合器输出中添加一个小变化的组合器,组合器的输出不被Reducer合并。场景如下:在组合器中,

    • 问题内容: 恐怕这是一个愚蠢的问题。 有谁能告诉我为什么没有对立的东西? 除了“因为根本就没有”以外,还有其他原因吗? 我应该创建自己的一个吗?还是我想念其他东西? 更新资料 在哪里使用?我正在编写一个使用大量消费者和供应商的图书馆。我成功地写了一行,并且遇到一种情况,期望消费者接受来自方法结果的布尔值。说什么 问题答案: 并且需要避免开销自动装箱每个值。处理原始图元更有效。但是,对于布尔值和字节

    • 问题内容: 在Java has 方法中,但是,它仅在诸如或的关联容器中使用。为什么要这样设计?具有方法的界面看起来更优雅。 问题答案: 在我看来,主要的论据是,可以为任何Java对象计算出一个定义明确的默认值,以及一个同样定义明确的。根本没有充分的理由要保留所有对象的该功能,当然也有很多理由 不 保留此功能。因此,这本书毫无疑问。

    • 问题内容: 在Java中,为什么以下代码行不起作用? 如果我将其更改为 起初,我以为您可能没有接口列表,但是我可以创建一个很好的接口。 有想法吗? 问题答案: 泛型类型比较古怪。 表示或任何子类型,但仅表示。如果您想要一个子类型,您需要 我怀疑你可以用 无法执行此操作的原因是,您可以使用对引用的引用,并且必须谨慎使用额外的间接级别。 使用泛型,您可以有两个间接级别,这会给您带来问题,因此它们更容易