Collect to map skipping null key/values

Joel :

Let's say I have some stream and want to collect to map like this

stream.collect(Collectors.toMap(this::func1, this::func2));

But I want to skip null keys/values. Of course, I can do like this

stream.filter(t -> func1(t) != null)
    .filter(t -> func2(t) != null)
    .collect(Collectors.toMap(this::func1, this::func2));

But is there more beautiful/effective solution?

Holger :

If you want to avoid evaluating the functions func1 and func2 twice, you have to store the results. E.g.

stream.map(t -> new AbstractMap.SimpleImmutableEntry<>(func1(t), func2(t))
      .filter(e -> e.getKey()!=null && e.getValue()!=null)
      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

This doesn’t make the code shorter and even the efficiency depends on the circumstances. This change pays off, if the costs of evaluating the func1 and func2 are high enough to compensate the creation of temporary objects. In principle, the temporary object could get optimized away, but this isn’t guaranteed.

Starting with Java 9, you can replace new AbstractMap.SimpleImmutableEntry<>(…) with Map.entry(…). Since this entry type disallows null right from the start, it would need filtering before constructing the entry:

stream.flatMap(t -> {
          Type1 value1 = func1(t);
          Type2 value2 = func2(t);
          return value1!=null && value2!=null? Stream.of(Map.entry(value1, value2)): null;
      })
      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

Alternatively, you may use a pair type of one of the libraries you’re already using (the Java API itself doesn’t offer such a type).

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=465335&siteId=1