Martin Magakian
Martin Magakian

Reputation: 3766

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());

Upvotes: 44

Views: 96291

Answers (4)

Holger
Holger

Reputation: 298143

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…

Upvotes: 80

Stuart Marks
Stuart Marks

Reputation: 132370

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.

Upvotes: 47

srborlongan
srborlongan

Reputation: 4579

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())
;

Upvotes: 3

Martin Magakian
Martin Magakian

Reputation: 3766

You can wrap the output into an Optional which may or may not contain a non-null value.
With an output: return Optional.of(out);
Without output: return Optional.<Integer>empty();

You have to wrap into an option because an array cannot contain any null value.

    List<Integer> input = Arrays.asList(0,1,2,3);
    List<Option<Integer>> output = input.stream()
                                .map(i -> { 
                                    Integer out = crazyFunction(i);
                                    if(out == null || out.equals(0))
                                        return Optional.<Integer>empty();
                                    return Optional.of(out);
                                    })
                                .collect(Collectors.toList());

This will make sure input.size() == output.size().

Later on you can exclude the empty Optional using:

    List<Integer> outputClean = output.stream()
                                   .filter(Optional::isPresent)
                                   .map(i -> {
                                           return i.get();
                                        })
                                   .collect(Collectors.toList());

Upvotes: 2

Related Questions