Java 8 为什么java Map.merge没有传递给供应商?

Java 8 为什么java Map.merge没有传递给供应商?,java-8,Java 8,在java中,我需要一个方法,允许我修改一个值(如果存在),或者插入一个值(如果不存在)。与“合并”类似,但: 我想传递一个价值供应商,而不是一个价值,以避免在不需要时创建它 如果该值存在,我不想重新插入或删除它,只需使用容器访问它的方法即可 我不得不写这个。我自己编写的问题是,并发映射的版本并不简单 public static <K, V> V putOrConsume(Map<K, V> map, K key, Supplier<V> ifAbsent,

在java中,我需要一个方法,允许我修改一个值(如果存在),或者插入一个值(如果不存在)。与“合并”类似,但:

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

    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;
    }
    
    public static V putOrConsume(地图地图、K键、供应商ifAbsent、消费者ifPresent){
    V val=map.get(键);
    如果(val!=null){
    如果存在,则接受(val);
    }否则{
    map.put(key,ifAbsent.get());
    }
    返回val;
    }
    
    实现它的最佳“标准”方法是使用compute()

    Map Map=newhashmap();
    双函数转换=(k,v)->v==null?“新的”+k:“旧的”+v;
    map.compute(“x”,convert);
    map.compute(“x”,convert);
    System.out.println(map.get(“x”))//打印旧的新的
    
    现在,比方说,你有你的供应商和消费者,并愿意遵循干燥的原则。然后您可以使用一个简单的函数组合器:

     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"
    
    Map Map=newhashmap();
    供应商ifAbsent=()->“新”;
    消费者ifPresent=System.out::println;
    双功能计算机消费=(k,v)->{
    if(v==null)返回ifAbsent.get();
    如果存在,接受(v);
    返回v;
    };
    map.compute(“x”,putOrConsume)//没有什么
    map.compute(“x”,putOrConsume)//打印“新”
    
    显然,您可以编写一个组合函数,它接受供应商和消费者并返回双函数,从而使上述代码更加通用

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


    map.compute(…)
    map.putIfAbsent(…)
    比相当专业化的建议的putOrConsume(…)强大得多。它是如此的不对称,我实际上会在代码中回顾您需要它的原因。

    您可以通过一个简单的助手方法,以及一个本地类的帮助来实现您想要的,以了解您的
    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;
    }
    
    publicstaticv putOrConsume(
    地图,
    K键,
    供应商(如有),
    消费者(如有){
    类缺席供应商实现供应商{
    布尔值=假;
    公共V get(){
    使用=正确;
    返回ifAbsent.get();
    }
    }
    缺席供应商缺席供应商=新缺席供应商();
    V computed=map.compute(
    钥匙
    (k,v)->v==null?
    缺席供应商。获取():
    (v,如果存在);
    返回缺席供应商。是否已使用?空:已计算;
    }
    专用静态V消费者和返回(V,消费者){
    消费者接受(v);
    返回v;
    }
    
    棘手的部分是查找您是否使用了
    ifAbsent
    供应商来返回
    null
    或现有的已消耗值


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

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

    用法
    //仅当值存在时使用
    消费者行动=。。。;
    compute(key,ValueMapping.ifPresent(action));
    //如果没有价值,就创造价值
    供应商=。。。;
    compute(key,ValueMapping.ifPresent(action.orElse(supplier));
    //如果缺少值,则从键映射值
    函数映射=。。。;
    compute(key,ValueMapping.ifPresent(action.orElse(mapping));
    //orElse支持短路功能
    compute(键,ValueMapping.ifPresent(操作)
    .orElse(供应商)
    .orElse(()->fail(“不应调用它”)+
    “如果以前的orElse计算的值”);
    T失败(字符串消息){
    抛出新断言错误(消息);
    }
    
    值映射
    接口值映射扩展了双功能{
    默认值映射orElse(供应商其他){
    返回orElse(k->other.get());
    }
    默认值映射orElse(函数其他){
    返回(k,v)->{
    R结果=此。应用(k,v);
    返回结果!=null?结果:其他。应用(k);
    };
    }
    静态值映射ifPresent(使用者操作){
    返回(k,v)->{
    如果(v!=null){
    行动.接受(v);
    }
    返回v;
    };
    }
    }
    
    注 在以前的版本中,我在
    ValueMapping
    中使用了
    Objects.isNull
    。@Holger指出,这是一个过度使用的情况,应该用更简单的条件取代它空

    大多数(相关的)
    Map
    实现以执行单个查找的方式覆盖
    compute
    的默认实现,如果键存在,换句话说,不需要“额外调用Map.put()”,即使更改值也是如此…
    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;
    }
    
    //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); 
    }
    
    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;
            };
        }
    }