Given the following 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?
You can create a custom Collector
(here called StreamInterceptor
), even though this does not really fit the purpose of a collector.
Stream<T>
to a List<T>
Stream<T>
of the List<T>
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. In your case it will just print the size of the list.
StreamInterceptor
classimport 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();
}
}
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 = 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
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:
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
.
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
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.