简体   繁体   中英

Java Streams; avoid finisher on Collectors.collectingAndThen

I've this code:


private Iterable<Practitioner> pickPractitioners(List<String> ids) {

    return Optional.ofNullable(ids)
        .map(List::stream)
        .orElse(Stream.of())
        .collect(
            Collectors.collectingAndThen(
                Collectors.toList(),
                this.practitionerRepository::findAllById
            )
        );

}

Problem is that when ids is empty, this.practitionerRepository::findAllById is also executed.

I'd like to avoid this step if resulting collector is empty.

Any ideas?

In general to skip that part of the finisher you could pass a lambda instead of a method reference and check if the input is empty:

    .collect(
        Collectors.collectingAndThen(
            Collectors.toList(),
            r -> r.isEmpty() ? Collections.emptyList() : this.practitionerRepository.findAllById(r)
        )
    );

If your actual code is a simple as this example then you don't need to use streams or optional at all. Instead you could just check if the input of the method is null or empty in a ternary operator:

    return ids == null || ids.isEmpty() ? Collections.emptyList() :
        this.practitionerRepository.findAllById(ids);

If you look at the signature of the Finisher. It is just a function, so you can just write it:

 public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream, Function<R,RR> finisher) {
static interface MyRepository extends JpaRepository<Part, Long> {

}

public static void main(String[] args) {

    MyRepository myRepository = null;
    List<Long> list = null;

    Function<List<Long>, List<Part>> finisher = (ids) -> {

        return ids.isEmpty() ? Collections.emptyList() : myRepository.findAllById(ids);

    };

    Optional.ofNullable(list)
            .map(List::stream)
            .orElse(Stream.of())
            .collect(
                    Collectors.collectingAndThen(
                            Collectors.toList(),
                            finisher
                    )
            );

}

Whilst the practical part of this question ( how to avoid interrogating the repository with an empty list as an argument ) is already addressed in other answers I want to point out that there's a cleaner way to build a pipeline in this method.

Firstly it's worthy to remind that the main purpose of Optional.ofNullable() is to create an Optional object that has to be returned from a method .

Attempts to use Optional.ofNullable() in order to utilize method-chaining or to avoid null-checks in the middle of the method according to Stuart Marks are considered to be anti-patterns.

Here is the quote from his talk at Devoxx :

"it's generally a bad idea to create an Optional for the specific purpose of chaining methods from it to get a value."

A similar idea was expressed in his answer on stackoverflow .

What are the alternatives?

Since Java 9 Stream interface has its own method ofNullable() .

Returns a sequential Stream containing a single element, if non-null, otherwise returns an empty Stream.

Kipping all that in mind method pickPractitioners() could be rewritten like this:

private Function<List<String>, Iterable<Practitioner>> getPractitioners =
        idList -> idList.isEmpty() ? Collections.emptyList() : 
                                     this.practitionerRepository.findAllById(idList);


private Iterable<Practitioner> pickPractitioners(List<String> ids) {

    return Stream.ofNullable(ids)
            .flatMap(List::stream)
            .collect(Collectors.collectingAndThen(
                            Collectors.toList(),
                            getPractitioners
            ));
}

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.

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