为什么java Map.merge不通过供应商?

时间:2017-05-19 20:36:50

标签: java-8

我想在java中使用一种方法,它允许我修改一个值(如果存在),或者如果它不存在则插入一个。与merge类似,但是:

  1. 我想传递价值供应商而不是价值,以避免在不需要时创建
  2. 如果值存在,我不想重新插入或删除它,只需使用容器访问其方法。
  3. 我不得不写这个。自己编写的问题是并发映射的版本并不简单

    public static <K, V> V putOrConsume(Map<K, V> map, K key, Supplier<V> ifAbsent, Consumer<V> ifPresent) {
        V val = map.get(key);
        if (val != null) {
          ifPresent.accept(val);
        } else {
          map.put(key, ifAbsent.get());
        }
        return val;
    }
    

3 个答案:

答案 0 :(得分:2)

实现它的最佳“标准”方法是使用compute():

 Map<String, String> map = new HashMap<>();

 BiFunction<String, String, String> convert = (k, v) -> v == null ? "new_" + k : "old_" + v;

 map.compute("x", convert);
 map.compute("x", convert);

 System.out.println(map.get("x")); //prints old_new_x

现在,比如说,您有供应商和消费者,并希望遵循DRY原则。然后你可以使用一个简单的函数组合器:

 Map<String, String> map = new HashMap<>();

 Supplier<String> ifAbsent = () ->  "new";
 Consumer<String> ifPresent = System.out::println;

 BiFunction<String, String, String>  putOrConsume = (k, v) -> {
        if (v == null) return ifAbsent.get();
        ifPresent.accept(v);
        return v;
 };
 map.compute("x", putOrConsume); //nothing
 map.compute("x", putOrConsume); //prints "new"

显然,你可以编写一个组合函数,它接受供应商和消费者,并返回BiFunction,使上面的代码更加通用。

这种提议方法的缺点在于对map.put()的额外调用,即使您只是在键查找时消耗该值,即它会稍微慢一些。好消息是,地图实现将简单地替换值而不创建新节点。即不会创建新对象或收集垃圾。大多数时候这种权衡是合理的。

map.compute(...)map.putIfAbsent(...)比相当专业的putOrConsume(...)强大得多。它是如此不对称我实际上会在代码中查看你需要它的原因。

答案 1 :(得分:0)

您可以使用Map.compute和一个简单的帮助方法,以及在本地课程的帮助下,了解您的ifAbsent供应商是否已被使用,从而实现您想要的目标:

public static <K, V> V putOrConsume(
        Map<K, V> map,
        K key,
        Supplier<V> ifAbsent,
        Consumer<V> ifPresent) {

    class AbsentSupplier implements Supplier<V> {
        boolean used = false;

        public V get() {
            used = true;
            return ifAbsent.get();
        }
    }
    AbsentSupplier absentSupplier = new AbsentSupplier();

    V computed = map.compute(
            key,
            (k, v) -> v == null ?
                    absentSupplier.get() :
                    consumeAndReturn(v, ifPresent));

    return absentSupplier.used ? null : computed;
}

private static <V> V consumeAndReturn(V v, Consumer<V> consumer) {
    consumer.accept(v);
    return v;
}

棘手的部分是找出您是否已使用ifAbsent供应商返回null或现有的消费值。

辅助方法只是调整ifPresent使用者,使其行为类似于使用给定值并返回它的一元运算符。

答案 2 :(得分:0)

与其他答案不同,您还使用Map.compute方法并将Function与界面默认方法/静态方法结合使用,以使您的代码更加可读。例如:

用法

//only consuming if value is present
Consumer<V> action = ...;
map.compute(key,ValueMapping.ifPresent(action));

//create value if value is absent
Supplier<V> supplier = ...;
map.compute(key,ValueMapping.ifPresent(action).orElse(supplier));

//map value from key if value is absent
Function<K,V> mapping = ...;
map.compute(key,ValueMapping.ifPresent(action).orElse(mapping));

//orElse supports short-circuit feature
map.compute(key,ValueMapping.ifPresent(action)
                            .orElse(supplier)
                            .orElse(() -> fail("it should not be called "+
                                 "if the value computed by the previous orElse")));

<T> T fail(String message) {
    throw new AssertionError(message); 
}

ValueMapping

interface ValueMapping<T, R> extends BiFunction<T, R, R> {
    default ValueMapping<T, R> orElse(Supplier<R> other) {
        return orElse(k -> other.get());
    }

    default ValueMapping<T, R> orElse(Function<T, R> other) {
        return (k, v) -> {
            R result = this.apply(k, v);
            return result!=null ? result : other.apply(k);
        };
    }

    static <T, R> ValueMapping<T, R> ifPresent(Consumer<R> action) {
        return (k, v) -> {
            if (v!=null) {
                action.accept(v);
            }
            return v;
        };
    }
}

注意

我在之前版本的Objects.isNull中使用了ValueMapping。和@Holger指出这是一个过度使用的情况,应该用更简单的条件it != null替换它。