简体   繁体   中英

Remove all entries where value is an empty Optional from map

I want to remove all entries where value is an empty Optional from the map. It would seem that nothing complicated, but I am trying to find a better solution that I have.


Input:

I have the following Map:

Map<String, Function<String, Optional<String>>> attributesToCalculate = new HashMap<>();

Where key - just a String and value - reference to method which returns Optional < String >


Output:

As a result, I want to get

Map<String, String> calculatedAttributes

(excluding entries where value was an empty Optional)


Here is my solution

      return attributesToCalculate.entrySet()
        .stream()
        .map(entry -> Pair.of(entry.getKey(), entry.getValue().apply(someString)))
        .filter(entry -> entry.getValue().isPresent())
        .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().get()));

But I don't like the .filter part because then I have to invoke .get() on Optional in collect part.

Is there а better way (maybe without .get invocation) to solve this problem? Thanks.

As commented above, there is nothing wrong with using get if you have already checked that the Optional is not empty.

However, I think this code is best expressed without using streams.

Map<String, String> result = new HashMap<>();
attributesToCalculate.forEach((k, v) ->
    v.apply(someString).ifPresent(str -> result.put(k, str))
);

If you don't like using forEach to populate the map in that way, you can use a simple for loop instead.

Using Guava, you can do this:

Maps.filterValues(
        Maps.transformValues(
                attributesToCalculate,
                f -> f.apply("someString").orElse(null)),
        Predicates.notNull())

Note that this returns a view of the underlying map, meaning any lookup or iteration will delegate to the function. If that's an issue, just copy the result to a new map.

Or you might consider StreamEx 's EntryStream :

EntryStream.of(attributesToCalculate)
        .mapValues(f -> f.apply("someString"))
        .flatMapValues(StreamEx::of)
        .toMap();

Not a very pretty one, and similar to for-loop:

 return attributesToCalculate.entrySet().stream().collect(HashMap::new, (sink, entry) -> {

    entry.getValue().apply(someString).ifPresent(v -> sink.put(entry.getKey(), v));

}, Map::putAll);

You could define a helper method to create your Pair:

public static <L, R> Optional<Pair<L, R>> of2(L left, Optional<R> right) {
    return right.map(r -> Pair.of(left, r));
}

Although this means to use Optional as a parameter, in this case it is not that bad imo (if you inlined the method, you wouldn't have it as a parameter...).

Then you could just do:

attributesToCalculate.entrySet()
     .stream()
     .map(entry -> of2(entry.getKey(), entry.getValue().apply(someString)))
     .flatMap(Optional::stream) // Java 9
     .collect(Collectors.toMap(Map.Entry::getKey, Pair::getValue));

What's wrong with using a plain old Iterator ? This doesn't look much more verbose than some of the other stream solutions.

final Map<String, Optional<Object>> attributesToCalculate = new HashMap<>();
final Map<String, Object> calculatedAttributes = new HashMap<>();

final Iterator<Entry<String, Optional<Object>>> iter = attributesToCalculate.entrySet().iterator();
while (iter.hasNext()) {
    final Entry<String, Optional<Object>> current = iter.next();
    if (current.getValue().isPresent()) {
        calculatedAttributes.put(current.getKey(), current.getValue().get());
    }
}

Or even shorter using a plain for loop:

for(Entry<String, Optional<Object>> current : attributesToCalculate.entrySet()) {
    if (current.getValue().isPresent()) {
        calculatedAttributes.put(current.getKey(), current.getValue().get());
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM