0

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.

Gautham M
  • 4,816
  • 3
  • 15
  • 37
Adam
  • 13
  • 2
  • Have you tried anything? A big part of being a good software developer is having the curiosity and initiative play with things to see how they work. I suggest that you read the relevant Javadoc (you do seem to have a basic grasp of streams, which is good), try some experiments, and when you find some behavior you don't understand, THEN ask a specific question. – Jim Garrison Apr 27 '21 at 23:57
  • Here was a failed attempt: ` list.stream().map(p -> getObjectForString(p)).map(Optional::ofNullable).forEach( p -> p.ifPresentOrElse( r -> doSomethingWithObject(r), () -> System.err.println("Object was null") ));` It looks like doSomethingWithObject is being called twice for each item in the list (including when the object is null). I do get one output of the error message for one null object, so that is good. But I am also not getting the String "item" appended to my error message as I would like (that may be the trickiest part) – Adam Apr 28 '21 at 00:48
  • Trying to properly edit the formatting of my previous comment. BUT, it timed out. Sorry about that mess. – Adam Apr 28 '21 at 00:53
  • 1
    @Adam, you could paste your failed attempt and related information as part of the question itself. – Gautham M Apr 28 '21 at 03:17
  • 2
    There is no sense in mapping to an `Optional`. Just use `.filter(item -> getObjectForString(item) == null)` – Holger Apr 28 '21 at 09:45
  • @Holger, but the point is if the object is null, then I want to log a message. Can't do that if I filter those out, right? – Adam Apr 28 '21 at 13:33
  • @GauthamM, good suggestion - added to the question. – Adam Apr 28 '21 at 13:39
  • 3
    The predicate passed to `filter` tells which elements to keep. The logic of `.filter( /* tell which elements should pass */) .forEach( /* tell what to do with the elements that passed the filter */)` should be easy to understand. But anyway, look at your attempt, and ask yourself, letting aside that it didn’t work that way, is this in any way simpler than your original loop? – Holger Apr 28 '21 at 13:39

3 Answers3

0

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))
                );
  • Thanks. I got sidetracked on this, but just now came back to it. I got it to do what I want using Pair. Thanks again. – Adam May 11 '21 at 14:46
0

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);
Gautham M
  • 4,816
  • 3
  • 15
  • 37
0

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));
Ron McLeod
  • 643
  • 7
  • 12