Java 8 泛型:将一个使用者流减少到单个使用者

2022-09-01 11:00:30

如何编写将 a 组合成单个使用的方法?StreamConsumersConsumerConsumer.andThen(Consumer)

我的第一个版本是:

<T> Consumer<T> combine(Stream<Consumer<T>> consumers) {
    return consumers
            .filter(Objects::nonNull)
            .reduce(Consumer::andThen)
            .orElse(noOpConsumer());
}

<T> Consumer<T> noOpConsumer() {
    return value -> { /* do nothing */ };
}

这个版本使用JavaC和Eclipse编译。但它太具体了:不能是 ,如果 不是确切的类型,而是它的超类型,则不能使用它:StreamStream<SpecialConsumer>ConsumersT

Stream<? extends Consumer<? super Foo>> consumers = ... ;
combine(consumers);

这是理所当然的,不会编译。改进的版本将是:

<T> Consumer<T> combine(Stream<? extends Consumer<? super T>> consumers) {
    return consumers
            .filter(Objects::nonNull)
            .reduce(Consumer::andThen)
            .orElse(noOpConsumer());
}

但是 Eclipse 和 JavaC 都没有编译:
Eclipse (4.7.3a):

类型未定义此处适用的类型ConsumerandThen(capture#7-of ? extends Consumer<? super T>, capture#7-of ? extends Consumer<? super T>)

JavaC (1.8.0172):

错误:不兼容类型:无效方法引用

不兼容类型:无法转换为
where 是类型变量:
在方法
中声明,其中 是新鲜的类型变量:.reduce(Consumer::andThen)Consumer<CAP#1>Consumer<? super CAP#2>TT extends Object<T>combine(Stream<? extends Consumer<? super T>>)CAP#1CAP#2
CAP#1 extends Object super: T from capture of ? super T
CAP#2 extends Object super: T from capture of ? super T

但它应该有效:Consumer的每个子类也可以用作Consumer。每个超类型X的消费者也可以消费X。我试图将类型参数添加到流版本的每行,但这无济于事。但是如果我用一个传统的循环把它写下来,它会编译:

<T> Consumer<T> combine(Collection<? extends Consumer<? super T>> consumers) {
    Consumer<T> result = noOpConsumer()
    for (Consumer<? super T> consumer : consumers) {
        result = result.andThen(consumer);
    }
    return result;
}

(为了简洁起见,省略了筛选出 null 值。

因此,我的问题是:我如何说服JavaC和Eclipse我的代码是正确的?或者,如果它不正确:为什么循环版本是正确的,而不是版本?Stream


答案 1

您可以使用具有以下签名的单参数 Stream.reduce(累加器)版本:

Optional<T> reduce(BinaryOperator<T> accumulator);

只能接受 类型的元素,但你有:BinaryOperator<T> accumulatorT

<? extends Consumer<? super T>>

我建议您改用 Stream.reduce(...) 方法的三参数版本:

<U> U reduce(U identity,
             BiFunction<U, ? super T, U> accumulator
             BinaryOperator<U> combiner);

可以接受两种不同类型的参数,具有较少的限制范围,更适合您的情况。一个可能的解决方案可能是:BiFunction<U, ? super T, U> accumulator

<T> Consumer<T> combine(Stream<? extends Consumer<? super T>> consumers) {
    return consumers.filter(Objects::nonNull)
                    .reduce(t -> {}, Consumer::andThen, Consumer::andThen);
}

第三个参数仅在并行流中调用,但无论如何,提供它的正确实现是明智的。BinaryOperator<U> combiner

此外,为了更好地理解,可以按如下方式表示上述代码:

<T> Consumer<T> combine(Stream<? extends Consumer<? super T>> consumers) {

    Consumer<T> identity = t -> {};
    BiFunction<Consumer<T>, Consumer<? super T>, Consumer<T>> acc = Consumer::andThen;
    BinaryOperator<Consumer<T>> combiner = Consumer::andThen;

    return consumers.filter(Objects::nonNull)
                    .reduce(identity, acc, combiner);
}

现在你可以写:

Stream<? extends Consumer<? super Foo>> consumers = Stream.of();
combine(consumers);

答案 2

你忘记了方法定义中的一件小事。它目前是:

<T> Consumer<T> combine(Stream<? extends Consumer<? super T>> consumers) {}

但是你正在反覆.因此,通过更改返回类型,它几乎可以正常工作。现在,您接受类型的参数。目前它不起作用,因为您正在使用可能不同的子类和实现(因为上界通配符)。你可以克服这一点,通过将每一个在你的一个简单的.如下所示:Consumer<? super T>consumersStream<? extends Consumer<? super T>>Consumer<? super T>extends? extends Consumer<? super T>StreamConsumer<? super T>

<T> Consumer<? super T> combine(Stream<? extends Consumer<? super T>> consumers) {
    return consumers
        .filter(Objects::nonNull)
        .map(c -> (Consumer<? super T>) c)
        .reduce(Consumer::andThen)
        .orElse(noOpConsumer());
}

现在应该可以工作了


推荐