简体   繁体   中英

Map getOrDefault VS getOrUseSupplier

I am starting to learn lambdas and i do not understand why java Map has:

getOrDefault(Object key, V defaultValue)

and not(working just the same, but if value is absent, then defaultValue will be taken from supplier):

getOrUseSupplier(Object key, Supplier<V> defaultValue)

Adventages i currently see of current solution:

  • defaultValue does not have to be a final/effectively final value
  • looks simpler & no need to know lambda syntax

Disadventages:

  • If while using getOrDefault we put new object creation, it will be created and passed to GC immidietely(while using supplier, it will not be created at all).

I would like to know if there is any more disadventages of using & having getOrDefault rather than getOrUseSupplier. Could you also tell me if anywhere in java libraries, there is method like this:

static <V> V getOrUseSupplier(Map<?, V> map, Object key, Supplier<V> supplier)

that tries to take Value from map, and if it does not exist then takes value from Supplier.

I guess it was because not to blow up the Map interface. You can use Optional.orElseGet(Supplier) as a workaround (if you don't keep nulls in your map):

Optional.ofNullable(map.get(key)).orElseGet(supplier)

The closest equivalent of getOrUseSupplier() in Map is named computeIfAbsent() which allows for the value to be computed using the key, giving more flexibility than if it only took a Supplier . It also stores the computed value in the Map , unlike getOrDefault . This is because they have distinct use cases and are not really related. While getOrDefault is usually used to return a "safe" non-null default value (such as returning empty string instead of a null) indicating that something should be in the map, computeIfAbsent() implies that something must be in the map, and if it's not, it needs to be created or otherwise the internal state of the program is not correct.

The following example ignores the key and just uses the supplier's value.

public static <V,T> V getOrUseSupplier(Map<T, V> map, T key, Supplier<V> supplier) {
    return map.computeIfAbsent(key, k -> supplier.get());
}

When lambdas were designed, a discussion was had on the lambda-dev mailing list on whether to include a Supplier equivalent to getOrDefault . Two reasons were given by Oracle's Brian Goetz for its exclusion from the API: to avoid feature creep of features that don't carry their own weight, and the cost of lambda capture.

Question

Sometimes constructing the default object is too costly and relying on the ternary operation is the escape hatch again. A Map.getOrDefault(Object,Supplier) would solve this.

Has this been considered before?

Feature creep

Feature creep alert!

getOrDefault barely, and I mean barely, carried its weight.

Cost

BTW, another reason we are reluctant to go Supplier-happy is that there is a mismatch between the actual and perceived cost models for lambda capture.

Capturing stateless (non-capturing) lambdas is essentially free.

Capturing stateful ones, such as

 () -> state.toString()

currently has a cost comparable to instantiating an inner class instance that captures a local variable. Since the only reason to provide the supplier version is cost, even though it may deliver significant cost reduction, it may not deliver as much as people expect it will. So this is a small consideration in favor of not leaning on this trick too heavily.

We are looking at VM work that will significantly remove these costs, but first we have to get 8 out the door.

When using the getOrDefault() method you should keep in mind that if your second argument (the default value) throws an exception for some reason, you will get that exception thrown even if the key exists in the map, as the default value is evaluated regardless of the existence of the key. Here is an example:

private static final Map<String, Integer> RANKS = Map.of(
        "A", 9,
        "B", 8,
        "C", 7,
        "D", 6
);

public static void main(final String[] args) {
    final int value1 = RANKS.getOrDefault("E", 5); // This will work
    final int value2 = RANKS.getOrDefault("A", Integer.valueOf("G")); // Exception!
}

As you can see even though the key A exists, it will still try to evaluate the default value anyway, which itself throws a NumberFormatException in this example.
If you use that hypothetical getOrUseSupplier() method (which unfortunately, as @Kayaman already explained, doesn't yet exist natively in Java, and you should implement it yourself), you can do this:

final int value = getOrUseSupplier(RANKS, "A", () -> Integer.parseInt("G")); // This will work

Now the exception will be thrown only if the key A is missing.

Objects.requireNonNullElseGet() is a nice way to achieve the desired getOrUseSupplier(Object key, Supplier<V> defaultValueSupplier) behavior.

Objects.requireNonNullElseGet(map.get(key), defaultValueSupplier));

Advantages (comparing to other solutions):

  • one-liner
  • has no side effects on map
  • doesn't create any garbage objects (like Optional)
  • core java (no libraries required)

Requires Java 9.

I'd be happy to have this convenient method getOrUseSupplier() on map itself too. Maybe in upcoming releases...

UPD

I think that this mail from Brian Goetz is the official answer to your original question. Java designers are afraid that programmers will abuse capturing lambdas using this method.

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