Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return first non empty list lazyily in Java 8

I have N lists that return data from a Repository. I want to return the first non empty of these three lists (each one executes a different SQL to fetch data).

The catch is that I want to do this lazily, so that I don't need to execute a SQL on the database if I have already found an acceptable result. My code is (modified)

@Override
public List<Something> dataService(Data data) {

return firstNonEmptyList(repository.getDataWayOne(data.getParameter()), 
                         repository.getDataWayTwo(data.getParameter()),
                         repository.getDataWayThree(data.getParameter().getAcessoryParameter())
                         Collections.singletonList(repository.getDefaultData(data.getParameter()));

}

@SafeVarargs
private final List<Something> firstNonEmptyList(List<Something>... lists) {
for (List<Something> list : lists) {
  if (!list.isEmpty()) {
    return list;
  }
}

return null;

}

This works, but it isn't lazy. Any ideas?

like image 904
rafael.braga Avatar asked Feb 23 '17 01:02

rafael.braga


2 Answers

You can make a stream of suppliers and evaluate them in encounter order until you find a result:

return Stream.<Supplier<List<Something>>>of(
            () -> repository.getDataWayOne(data.getParameter()),
            () -> repository.getDataWayTwo(data.getParameter()),
            () -> repository.getDataWayThree(data.getParameter().getAcessoryParameter()),
            () -> Collections.singletonList(repository.getDefaultData(data.getParameter()))
        )
        .map(Supplier::get)
        .filter(l -> !l.isEmpty())
        .findFirst()
        .orElse(null);

Each supplier defines how to access a result set, without actually attempting it until map() is executed. Since filter() and map() are stateless operations, each supplier will be called and its result validated before the next one is attempted. If a non-empty result is found, the stream will terminate immediately, because findFirst() is short-circuiting.

like image 181
shmosel Avatar answered Oct 12 '22 01:10

shmosel


If streams aren't your cup of tea, you can still use lambdas to achieve what you want with only some slight modifications to your original code.

public List<Something> dataService(Data data) {
    return firstNonEmptyList(
            () -> repository.getDataWayOne(data.getParameter()),
            () -> repository.getDataWayTwo(data.getParameter()),
            () -> repository.getDataWayThree(data.getParameter().getAcessoryParameter()),
            () -> Collections.singletonList(repository.getDefaultData(data.getParameter()))
        );
}

private final List<Something> firstNonEmptyList(Supplier<List<Something>>... listSuppliers) {
    for (Supplier<List<Something>> supplier : listSuppliers) {
        List<Something> list = supplier.get();
        if (!list.isEmpty()) {
            return list;
        }
    }
    return null;
}
like image 30
Kevin K Avatar answered Oct 12 '22 01:10

Kevin K