Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return empty element from Java 8 map operation

Using Java 8 stream what is the best way to map a List<Integer> when you have no output for the input Integer ?

Simply return null? But now my output list size will be smaller than my input size...

    List<Integer> input = Arrays.asList(0,1,2,3);
    List<Integer> output = input.stream()
                                .map(i -> { 
                                    Integer out = crazyFunction(i);
                                    if(out == null || out.equals(0))
                                        return null;
                                    return Optional.of(out);
                                    })
                                .collect(Collectors.toList());
like image 382
Martin Magakian Avatar asked Jul 25 '14 02:07

Martin Magakian


People also ask

How do I return a null in Java 8?

collectingAndThen( Collectors. joining(), str->{ if(str. isEmpty()) return null; return str; } ) );

Can I Stream an empty List?

toList()) , you'll always get an output List (you'll never get null ). If the Stream is empty (and it doesn't matter if it's empty due to the source of the stream being empty, or due to all the elements of the stream being filtered out prior to the terminal operation), the output List will be empty too.

Does collectors toList return empty List?

Collector. toList() will return an empty List for you. As you can see ArrayList::new is being used as a container for your items.

What is the return type of map in Java 8?

The map() is an intermediate operation. It returns a new Stream as return value. The map() operation takes a Function , which is called for each value in the input stream and produces one result value, which is sent to the output stream.


3 Answers

I don’t get why you (and all answers) make it so complicated. You have a mapping operation and a filtering operation. So the easiest way is to just apply these operation one after another. And unless your method already returns an Optional, there is no need to deal with Optional.

input.stream().map(i -> crazyFunction(i))
              .filter(out -> out!=null && !out.equals(0))
              .collect(Collectors.toList());

It may be simplified to

input.stream().map(context::crazyFunction)
              .filter(out -> out!=null && !out.equals(0))
              .collect(Collectors.toList());

But you seem to have a more theoretical question about what kind of List to generate, one with placeholders for absent values or one with a different size than the input list.

The simple answer is: don’t generate a list. A List is not an end in itself so you should consider for what kind of operation you need this list (or its contents) and apply the operation right as the terminal operation of the stream. Then you have your answer as the operation dictates whether absent values should be filtered out or represented by a special value (and what value that has to be).

It might be a different answer for different operations…

like image 153
Holger Avatar answered Oct 13 '22 23:10

Holger


Replace the map call with flatMap. The map operation produces one output value per input value, whereas the flatMap operation produces any number of output values per input value -- include zero.

The most straightforward way is probably to replace the check like so:

List<Integer> output = input.stream()
                            .flatMap(i -> { 
                                Integer out = crazyFunction(i);
                                if (out == null || out.equals(0))
                                    return Stream.empty();
                                else
                                    return Stream.of(out);
                                })
                            .collect(Collectors.toList());

A further refactoring could change crazyFunction to have it return an Optional (probably OptionalInt). If you call it from map, the result is a Stream<OptionalInt>. Then you need to flatMap that stream to remove the empty optionals:

List<Integer> output = input.stream()
    .map(this::crazyFunctionReturningOptionalInt)
    .flatMap(o -> o.isPresent() ? Stream.of(o.getAsInt()) : Stream.empty())
    .collect(toList());

The result of the flatMap is a Stream<Integer> which boxes up the ints, but this is OK since you're going to send them into a List. If you weren't going to box the int values into a List, you could convert the Stream<OptionalInt> to an IntStream using the following:

flatMapToInt(o -> o.isPresent() ? IntStream.of(o.getAsInt()) : IntStream.empty())

For further discussion of dealing with streams of optionals, see this question and its answers.

like image 36
Stuart Marks Avatar answered Oct 13 '22 23:10

Stuart Marks


Simpler variants of @Martin Magakian 's answer:

List<Integer> input = Arrays.asList(0,1,2,3);
List<Optional<Integer>> output =
  input.stream()
    .map(i -> crazyFunction(i)) // you can also use a method reference here
    .map(Optional::ofNullable) // returns empty optional
                               // if original value is null
    .map(optional -> optional.filter(out -> !out.equals(0))) // return empty optional
                                                           // if captured value is zero
    .collect(Collectors.toList())
;

List<Integer> outputClean =
  output.stream()
    .filter(Optional::isPresent)
    .map(Optional::get)
    .collect(Collectors.toList())
;
like image 3
srborlongan Avatar answered Oct 13 '22 21:10

srborlongan