简体   繁体   中英

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?

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);

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"));
    }
}

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.

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