here_to_learn
here_to_learn

Reputation: 189

Java Map - log message when key is not found in getOrDefault

I have a Map<String, List<SomeClass>> someMap and I'm retrieving the value based on someKey and for each element of the list of SomeClass I'm performing other operations.

someMap.getOrDefault(someKey, new ArrayList<>()).forEach(...)

I also want to be able to log messages when I don't find someKey. How would I be able to achieve it optimally? Is there any other function/way to achieve this behavior?

Upvotes: 4

Views: 1317

Answers (3)

NoDataFound
NoDataFound

Reputation: 11969

I think you have two choices:

Either you use a wrapper method, doing the actual call (getOrDefault, etc) and handling missing keys.

public static <K,V> V getOrDefault(Map<K,V> map, K key, V defaultValue) {
  V value = map.get(key);
  if (value == null) {
    logMissingValue(key);
    return defaultValue;
  }
  return value;
}

Or you create new implementation of Map doing just that, with a delegation to method that should be delegated (I won't do here in this example, but Eclipse work pretty well: Alt + Shift + S > Create delegate methods):

class LoggerMap<K,V> implements Map<K,V> {
  private final Map<K,V> internal;
  public LoggerMap(Map<K,V> internal) {
    this.internal = Objects.requireNonNull(internal, "internal");
  }

  @Override
  public V getOrDefault(K key, V defaultValue) {
    ... if not found logMissingValue(key); ...
  }
}

Now about which is optimal, that depends on your needs: if you know you will always use the wrapper method, then your missing keys will always be logged. Creating a new map implementation would be overkill.

If your need is to log absolutely all missing keys - even if foreign code (for example, some API taking a map as a parameter), then your best choice is a map implementation:

  • In terms of performance, I don't think you should worry about delegation: I did not test it using a benchmark, but the JVM should be able to optimize that.
  • There are other parts where a key might return a missing value (eg: remove, get, ...), using such an implementation will allow you to easily trace those as well.

Upvotes: 2

Harshal Parekh
Harshal Parekh

Reputation: 6017

Map<String, List<String>> map = new HashMap<>();
List<String> l = new ArrayList<>();
l.add("b");
map.put("a", l);

Yes, you can do it in a single statement. Use .compute().

map.compute("a", (k, v) -> {
    if (v == null) {
        System.out.println("Key Not Found");
        return new ArrayList<>();
    }
    return v;
}).forEach(System.out::println);

There's also computeIfAbsent() which will only compute the lambda if the key is not present.


Note, from the documentation:

Attempts to compute a mapping for the specified key and its current mapped value (or null if there is no current mapping).

This will add the key which was not found in your map.

If you want to remove those keys later, then simply add those keys to a list inside the if and remove them in one statement like this:

map.keySet().removeAll(listToRemove);

Upvotes: 6

Mansur
Mansur

Reputation: 1829

You can create a function to do that. For example, I created a function which will get the value from the map, return it if it is not null, or an empty list otherwise. Before returning the empty list, you can run a Runnable action. The main benefit of that is that you can do more than just logging there.

@Slf4j
public class Main {

    public static Collection<String> retrieveOrRun(Map<String, Collection<String>> map, String key, Runnable runnable) {
        final Collection<String> strings = map.get(key);
        if (strings == null) {
            runnable.run();
            return Collections.emptyList();
        } else {
            return strings;
        }
    }

    public static void main(String[] args) {
        Map<String, Collection<String>> map = new HashMap<>();
        Collection<String> strings = retrieveOrRun(map, "hello", () -> log.warn("Could not find a value for the key : {}", "hello"));
    }
}

Upvotes: 2

Related Questions