16

I am newbie here and trying my way hard through java 8 streams + lambda. Can someone help me with the below piece of code? Need to find the age of first person from the list which is optional. This list could be null.

Missing from the code - null check for the first element of the list. Would like to have this check as well.

Something equivalent to below piece of code.

//input parameter 
//Optional<List<Person>> persons
public Integer getAgeOfFirstPerson(Optional<List<Person>> persons) {
    if (!persons.isPresent()) {
        return null;
    }

    Integer age = persons.get()
        .get(0)
        .getAge();

    return age;
}

//Person.java
class Person {
    Integer age;
    //getters and setters
}
meso
  • 493
  • 2
  • 5
  • 15
  • 7
    I quote, don't use [`Optional` as args](https://stackoverflow.com/questions/51823194/how-to-map-an-optionallong-to-an-optionallong#comment90601909_51823194) – Naman Aug 22 '18 at 18:09
  • Thanks for the note :) – meso Aug 22 '18 at 19:48
  • 1
    Maybe more debatable, but you should probably avoid `Optional>` the same way you should avoid `null` `List` variables: is there really a difference between an empty `Optional>` and a non-empty one containing an empty `List`? If not, don't use `Optional` at all, use the empty `List` instead. If yes, maybe you should define your own type to clearly describe it. – Didier L Aug 22 '18 at 23:00
  • @DidierL I disagree with the "if yes" part: `Optional>` already describes it perfectly clearly. OTOH, I was never convinced by the arguments referenced in https://stackoverflow.com/a/24564612/9204 :) – Alexey Romanov Aug 23 '18 at 06:35
  • @AlexeyRomanov what does “`Optional>`” describe? Is there a semantic difference between an absent list and an empty list? – Holger Aug 23 '18 at 07:58

3 Answers3

30

Assuming the List inside your Optional can be empty, then you should account for this case as well. The easiest would thus be:

return persons.flatMap(list -> list.stream().findFirst()).map(Person::getAge).orElse(null);

This unfortunately does not handle a null first list entry as findFirst() throws a NullPointerException when the first element of a Stream is null (as Holger noted in the comments).

So if you want to skip null list entries and take the first non-null one, do as follows:

return persons.flatMap(list -> list.stream().filter(Objects::nonNull).findFirst())
              .map(Person::getAge)
              .orElse(null);

Otherwise, if instead you always want the first entry even when it is null, you could adapt it like this:

return persons.flatMap(list -> list.stream().limit(1).filter(Objects::nonNull).findFirst())
              .map(Person::getAge)
              .orElse(null);

however this really becomes quite convoluted, so it is simpler to just get back to testing whether the list is empty:

return persons.filter(list -> !list.isEmpty())
              .map(list -> list.get(0))
              .map(Person::getAge)
              .orElse(null);
Didier L
  • 18,905
  • 10
  • 61
  • 103
  • @Holger, I had forgotten that. I think there as well the recommendation was to avoid `null` items in streams. Anyway, I have updated the answer accordingly. – Didier L Aug 23 '18 at 08:23
  • 2
    Yes, that can be generalized to any kind of data structure with a variable number of elements, including possibly zero. Such a structure should never be `null` and never contain `null`, as you can express the same by not having these elements or even no elements at all, in most use cases. So for the `List` parameter of this question, it should not be `Optional` in the first place, as the list can be empty instead of absent. – Holger Aug 23 '18 at 08:41
  • @Holger, I can't agree more with you – Didier L Aug 23 '18 at 08:45
1

You could use Optional.map

public int getAgeOfFirstPerson(Optional<List<Person>> persons){ 
    return persons.map(people -> people.get(0).getAge()).orElse(0);
}

Do note, primitives do not have null(they are not objects), hence have replaced that with 0.

Or further improved as answered by @Alexey, if need be to check for Person being null as well.

Naman
  • 27,789
  • 26
  • 218
  • 353
1

return null won't compile if int is the return type. Assuming you want an absent Optional<Integer> instead,

return persons.map(list -> list.get(0)).map(Person::getAge)

will work (unfortunately, there's no mapToInt returning OptionalInt like for streams). Two maps are needed to handle

Missing from the code - null check for the first element of the list. Would like to have this check as well.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487