Using Java Optional within stream mapping

744 Views Asked by At

I have code like this:

public void processList(List<String> list) {

    for (String item : list) {
        Object obj = getObjectForString(item);
        if (obj != null) {
            doSomethingWithObject(obj);
        } else {
            System.err.println("Object was null for " + item);
        }
    }
}

Ideally I would like to streamline this and avoid the null check using list.stream().map( *blah, blah, blah* ), and doSomethingWithObject if the object is not null, but log the error otherwise (by using the orElse method on an optional). I'm not super savvy with this Java 8 functionality and not sure if there is a nice, slick way to do what I want here or not. Suggestions?

Edit to add a failed attempt at this:

list.stream()
    .map(p -> getObjectForString(p))
    .map(Optional::ofNullable)
    .forEach(
        p -> p.ifPresentOrElse(
            r -> doSomethingWithObject(r),
            () -> System.err.println("Object was null")
        ));

Even if that code behaved the way I want, it still doesn't append the String from the original list to the error message as I would like it to. But maybe that's too much complexity to try to accomplish with streams like this.

3

There are 3 best solutions below

1
On BEST ANSWER

we should propagate the item even after conversion. The slick way is using tuple or pair.

I used Tuple from vavr functional library to do the same. And below is the code for your reference

list.stream()
                .map(p -> Tuple.of(p, getObjectForString(p)).map2(Optional::ofNullable))
                .forEach(p -> p._2.ifPresentOrElse(
                            r -> doSomethingWithObject(r),
                            () -> System.err.println("Object was null" + p._1))
                );
0
On

Even though the below method does not avoid a null check as you wanted in your question, this is just another way to achieve the same result. (Only benefit is that it saves 1-2 lines of code!).

The below code uses Runnable (takes no arguments and returns nothing as well) along with Java 8's Function.

NOTE : I would still recommend the normal for loop :-), as I believe that the below might look fancy, but the for loop is more easy to understand in this particular case.

Function<String, Runnable> func = item -> {
    Object obj = getObjectForString(item);
    return (obj != null) ? ( () -> doSomethingWithObject(obj))
                         : ( () -> System.err.println("Object was null for " + item));        
};

list.stream().map(func).forEach(Runnable::run);
2
On

Another approach would be to collect the items in to separate 2 buckets/partitions based on if the item had an associated object or not. After that, process the 2 buckets as required:

final Boolean HAS_OBJECT = Boolean.FALSE;

Map<Boolean, List<String>> partitionedMap = list.stream()
        .collect(Collectors.partitioningBy(item -> !Objects.isNull(getObjectForString(item))));

partitionedMap.get(HAS_OBJECT).stream()
    .map(item -> getObjectForString(item))
    .forEach(obj -> doSomethingWithObject(obj));

partitionedMap.get(!HAS_OBJECT)
    .forEach(item -> System.err.println("Object was null for " + item));