java

Java findAny with Optional


var a = Optional.of(new Detail().getContacts()) // Optional<Set<String>>
    .stream() // Stream<Set<String>>
    .findAny() // Optional<Set<String>>
    .map(o -> "withAnyVal")
    .orElse(null);
var b = Optional.of(new Detail().getContacts()) // Optional<Set<String>>
    .stream() // Stream<Set<String>>
    .flatMap(Collection::stream) // Stream<String>
    .findAny() // Optional<String>
    .map(o -> "withAnyVal")
    .orElse(null);
var c = Optional.of(new Detail())
    .map(Detail::getContacts)
    .stream()
    .flatMap(Collection::stream)
    .findAny()
    .map(o -> "withAnyVal")
    .orElse(null);

Given: getContacts() is an empty collection.

Question: Why does the first one return "withAnyVal"? Shouldn't it fail in findAny and go to orElse which returns null?

Only b and c give the correct values null.


Solution

  • Optional.of(ArrayList<>) will return Optional<ArrayList<Object>>.

    Refer: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html#stream()

    Optional.stream() - will return a stream of object if it exists. So, even if the list is empty, stream() will return a stream of empty list.

    findAny() will return that list.

    Change that map(o -> "withAnyVal") to map(o -> "withAnyVal --> " + o) to see what you are getting as o. You should get [] (empty list) as found any object.

    flatMap results in making that stream of lists an empty stream (as the list element in the stream is empty, so flatMap results in empty stream). Without flatMap, the stream has an empty list as an element.

    Compare it with this:

    int[][] ary = { {} };
    System.out.println(ary.length);
    

    This will return 1. The array is not empty. The one element inside that array is an array and that array is empty. If you flatten this array, you get {} - and hence 0 length. That's what flatMap does. After flattening, findAny() won't get anything. Without flatMap, findAny() will get that array as it is the only element in the outer list.