45

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

4 Answers 4

81

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…

Sign up to request clarification or add additional context in comments.

4 Comments

A small detail here: when your "map" return a null value, it isn't added to the list. So you get less element in input then input. And thanks/because of that no point to check if out!=null in you case. It can't be null.
@Martin Magakian: Are you sure? I don’t see any automatic filtering of null documented, neither on the map function nor the toList collector. And a quick test on my machine reveals the map function works 1:1 just as expected. You have to filter or flatMap explicitly if you want a differently sized result list.
you are actually true! Well my question don't make sense anymore... hahha
This is quite simple. Further, if you just want to filter null return values, then we could simplify a .filter(out -> out != null) to .filter(Objects::nonNull) as well.
47

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.

6 Comments

Is there an advantage to using flatMap here rather than simply using .filter(o -> o.isPresent()) to filter out the nulls?
Not creating the null item representations in the first place is clearly an advantage in and of itself.
@Eran Mainly it's a matter of style, I think. If you have a mapping operation that sometimes doesn't want to return a result, using flatMap to return zero-or-one values might make sense. Returning Optional values to the stream followed by filter(Optional::isPresent).map(Optional::get) works and is simple and concise but might not align well with a might-return-no-value mapper. But it's certainly a value technique.
@StuartClark: if you want to know, which one is faster, measure, don’t guess, especially don’t guess based on fundamental misunderstanding of how streams work. There is no such thing as “parse over the values in the stream again”.
@Holger,@StuarClark: I measured map(...).filter(out -> out!=null && !out.equals(0)) and flatMap(...). The average results for different Listsizes(where the first part of the ratio is the measured time of the filter-method and the second part of the flatMap-method, all approximately) -> 10.000 List-Elements = 120ms:10ms, 100.000 = 130ms:60ms, 1.000.000= 160ms:100ms. Big surprise at 10.000.000=500ms:5sec. So a gut-feeling is, that Holgers's approach performs at really huge lists like 10.000.000 elements.
|
3

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

Comments

2

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.