Java 8 Generics:将消费者流量减少到单个消费者

时间:2018-06-18 11:53:55

标签: java generics java-8 java-stream

如何使用Stream编写一种方法,将Consumers Consumer Consumer.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 */ }; }

我的第一个版本是:

Stream

此版本使用JavaC和Eclipse编译。但它太具体了:Stream<SpecialConsumer>不能是Consumers,如果T不是类型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());
}

这不会合理地编译。改进版将是:

Consumer

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

  

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

JavaC(1.8.0172):

  

错误:不兼容的类型:无效的方法引用
  Consumer<CAP#1>
    不兼容的类型:Consumer<? super CAP#2>无法转换为T
  其中T extends Object是一个类型变量:
    方法<T>combine(Stream<? extends Consumer<? super T>>)中声明CAP#1   其中CAP#2CAP#1 extends Object super: T from capture of ? super T是新的类型变量:
    CAP#2 extends Object super: T from capture of ? super T
    <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; }

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

Stream

(为了简明起见,省略了过滤空值。)

因此,我的问题是:我怎样才能说服JavaC和Eclipse我的代码是正确的?或者,如果不正确:为什么循环版本正确但不是class Leave_Management(models.Model): employee = models.ForeignKey('employeeModel', on_delete=models.CASCADE) name = models.CharField(max_length=50) reason = models.TextField(max_length=200) date_to = models.DateField(null=True) date_from = models.DateField(null=True) leave_balance = models.IntegerField(default=14) paid_leave = models.IntegerField(default=0) unpaid_leave = models.IntegerField(default=0) time_generated = models.DateTimeField(auto_now_add=True) time_approved = models.DateTimeField(null=True, blank=True) def get_absolute_url(self): return reverse('approve',kwargs={'pk':self.pk}) def __str__(self): return str(self.employee) def get_total_days(self): dt = (self.date_to - self.date_from).days return dt 版本?

3 个答案:

答案 0 :(得分:27)

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

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

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

<? 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);

答案 1 :(得分:1)

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

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

但你正在回归Consumer<? super T>。所以通过改变返回类型它几乎可以工作。现在您接受consumers类型的参数Stream<? extends Consumer<? super T>>。目前它不起作用,因为您正在使用Consumer<? super T>的可能不同的子类和实现(因为上边界的通配符extends)。您可以通过将? extends Consumer<? super T>中的每个Stream转换为简单的Consumer<? 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());
}

现在应该可以了

答案 2 :(得分:0)

如果您有很多消费者,应用Consumer.andThen()将创建一个庞大的消费者包装树,以递归方式处理以呼叫每个原始消费者。

因此,简单地构建一个消费者列表并创建一个迭代它们的简单消费者可能会更有效:

<T> Consumer<T> combine(Stream<? extends Consumer<? super T>> consumers) {
    List<Consumer<? super T>> consumerList = consumers
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
    return t -> consumerList.forEach(c -> c.accept(t));
}

或者,如果您可以保证生成的消费者只会被调用一次,并且Stream在那时仍然有效,您可以直接在流上迭代:

return t -> consumers
        .filter(Objects::nonNull)
        .forEach(c -> c.accept(t));