简体   繁体   English

如果 Stream 中的 stream 为空,如何记录消息?

[英]How to log a message if a stream is empty within the Stream?

Given the following Java 8 Stream:给定以下 Java 8 Stream:

scheduleService.list().stream()
                      .filter(Schedule::getEnabled)
                      .filter(this::runnable)
                      .flatMap(s -> s.getJobs().stream())
                      // .doSomethingArbitrary(System.out.println("A single message. The total number of 
                      // elements in the stream after filtering is " + this::count))
                      .forEach(this::invoke);

After the filtering has been applied to the stream and after the first terminal operation has been applied, I would like to either log a debug message if the stream is empty or if it isn't, call the invoke method on each element in the stream. Is this possible?在将过滤应用于 stream 并应用第一个终端操作之后,如果 stream 为空,我想记录一条调试消息,或者如果不是,则对 stream 中的每个元素invoke方法。 这可能吗?

You can create a custom Collector (here called StreamInterceptor ), even though this does not really fit the purpose of a collector.您可以创建一个自定义Collector (此处称为StreamInterceptor ),即使这并不真正符合收集器的目的。


What will the custom collector do?自定义收集器将做什么?

  1. Convert the Stream<T> to a List<T>Stream<T>转换为List<T>
  2. Call the Consumer<List>, which will in your case print the length of the list.调用 Consumer<List>,在您的情况下它将打印列表的长度。
  3. Return a new Stream<T> of the List<T>返回List<T>的新Stream<T> T>

Main method主要方法

Here I've just broken down your problem into the filtering of a simple string list and printing them to the console at the end.在这里,我刚刚将您的问题分解为过滤一个简单的字符串列表,并在最后将它们打印到控制台。

    public static void main(String[] args) {
        List<String> myList = List.of("first", "second", "third");
        myList.stream()
                .filter(string -> !string.equals("second"))
                .collect(printCount())
                .forEach(System.out::println);
    }

    /**
     * Creates a StreamInterceptor, which will print the length of the stream 
     */
    private static <T> StreamInterceptor<T> printCount() {
        Consumer<List<T>> listSizePrinter = list -> System.out.println("Stream has " + list.size() + " elements");
        return new StreamInterceptor<>(listSizePrinter);
    }

When initializing the StreamInterceptor you can define a Consumer, that takes in the intermediate list constructed from the stream and performs some action on it.在初始化StreamInterceptor时,您可以定义一个 Consumer,它接收从 stream 构造的中间列表并对其执行一些操作。 In your case it will just print the size of the list.在您的情况下,它只会打印列表的大小。

New StreamInterceptor class新的StreamInterceptor class

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.*;
import java.util.stream.Collector;
import java.util.stream.Stream;

class StreamInterceptor<T> implements Collector<T, List<T>, Stream<T>> {

    private final Consumer<List<T>> listConsumer;

    public StreamInterceptor(Consumer<List<T>> listConsumer) {
        this.listConsumer = listConsumer;
    }

    @Override
    public Supplier<List<T>> supplier() {
        return ArrayList::new;
    }

    @Override
    public BiConsumer<List<T>, T> accumulator() {
        return List::add;
    }

    @Override
    public BinaryOperator<List<T>> combiner() {
        return (list1, list2) -> {
            list1.addAll(list2);
            return list1;
        };
    }

    @Override
    public Function<List<T>, Stream<T>> finisher() {
        return list -> {
            listConsumer.accept(list);
            return list.stream();
        };
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Collections.emptySet();
    }
}

Resources资源

You could always define a custom mapper that does the logging: 您始终可以定义执行日志记录的自定义映射器:

public static Stream<Job> loggingMapper(Service service) {
    if( service.getJobs().isEmpty() ) {
        System.out.println("Empty!"); // Or some other logging code
    }
    return service.getJobs().stream();
}

Then 然后

 // same
.flatMap(SomeClass::loggingMapper)

In any case your mapper has to return a stream. 无论如何,您的映射器必须返回一个流。

You could wrap your Stream into a custom method like the following 您可以将Stream包装成自定义方法,如下所示

Stream<???> stream = scheduleService.list().stream()
                                           .filter(Schedule::getEnabled)
                                           .filter(this::runnable)
                                           .flatMap(s -> s.getJobs().stream());

forEachOrElse(stream, this::invoke, () -> System.out.println("The stream was empty"));

With forEachOrElse being 有了forEachOrElse

public <T> void forEachOrElse(Stream<T> inStream, Consumer<T> consumer, Runnable orElse) {
    AtomicBoolean wasEmpty = new AtomicBoolean(true);
    inStream.forEach(e -> {
        wasEmpty.set(false);
        consumer.accept(e);
    });

    if (wasEmpty.get())
        orElse.run();
}

I can't test it right now but it should do its magic 我现在无法测试它,但它应该发挥它的魔力

This isn't really "nice" at all, but you could use peek to look into your stream and set an AtomicBoolean: 这根本不是“好”,但您可以使用peek来查看您的流并设置AtomicBoolean:

AtomicBoolean empty = new AtomicBoolean(true);

scheduleService.list().stream()
                      .filter(Schedule::getEnabled)
                      .filter(this::runnable)
                      .flatMap(s -> s.getJobs().stream())
                      .peek(s -> ab.set(false);)
                      .forEach(this::invoke);

if(empty.get()){
   // is Empty
}

You can add a peek before converting the list to stream . 您可以在将列表转换为stream之前添加peek

public static void main(String[] args) {
    Stream.of(Arrays.asList(1, 2, 3), Arrays.asList(4, 5), Collections.emptyList())
            .filter(x -> x.size() % 2 == 0)
            .peek(s -> System.out.println(s.isEmpty()))
            .flatMap(Collection::stream)
            .forEach(System.out::println);
}

Output 产量

false
4
5
true

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM