Java编程网

分享 Java Web 开发相关知识

Map.merge()-一种将其全部规则化的方法

我通常不会在JDK中解释一个方法,但是当我这样做时,它就差不多Map.merge()在键值范围中可能是功能最丰富的操作。而且还比较晦涩,很少使用。merge()可以解释如下:将新值放在给定键下(如果不存在),或者用给定值更新现有键(UPSERT)。让我们从最基本的示例开始:计算唯一单词的出现次数。Java 8之前的版本(阅读:2014之前!)非常混乱,实现细节中的实质丢失了:

var map = new HashMap <String,Integer>();
words.forEach(word-> {
    var prev = map.get(word);
    如果(上一个== null){
        map.put(word,1);
    }其他{
        map.put(word,prev + 1);
    }
});

但是,它可以工作,并且对于给定的输入会产生所需的输出:

var words = List.of(“ Foo”,“ Bar”,“ Foo”,“ Buzz”,“ Foo”,“ Buzz”,“ Fizz”,“ Fizz”);
//...
{Bar = 1,Fizz = 2,Foo = 3,Buzz = 2}

好的,但是让我们尝试重构它以避免条件逻辑:

words.forEach(word-> {
    map.putIfAbsent(word,0);
    map.put(word,map.get(word)+ 1);
});

真好!putIfAbsent()是必不可少的恶魔,否则,代码将在第一次出现以前未知的单词时中断。另外,我发现map.get(word)里面map.put()有点尴尬。让我们也摆脱它!

words.forEach(word-> {
    map.putIfAbsent(word,0);
    map.computeIfPresent(word,(w,prev)-> prev + 1);
});

computeIfPresent()仅当word存在问题()的键时,才调用给定的转换。否则什么都不做。我们通过将密钥初始化为零来确保密钥存在,因此增量始终有效。我们可以做得更好吗?我们可以减少额外的初始化,但是我不建议这样做:

words.forEach(word->
        map.compute(word,(w,prev)-> prev!= null?prev + 1:1)
);

compute()类似于computeIfPresent(),但是无论给定键是否存在都被调用。如果键的值不存在,则prev参数为null。将简单的if表达式转换为隐藏在lambda中的三元表达式远非最佳。这是merge()操作员大放异彩的地方。在向您展示最终版本之前,让我们来看一个稍微简化的默认实现Map.merge()

默认的V合并(K键,V值,BiFunction <V,V,V> remappingFunction){
    V oldValue = get(key);
    V newValue =(oldValue ==空)值:
               remappingFunction.apply(oldValue,value);
    如果(newValue == null){
        remove(key);
    }其他{
        put(key,newValue);
    }
    返回newValue;
}

该代码段价值一千个单词。merge()在两种情况下工作。如果给定的密钥不存在,它将变成put(key, value)。但是,如果所述密钥已经具有一定的价值,我们remappingFunction可能会合并(废除!)旧密钥和旧密钥。此功能可免费用于:

  • 只需返回新值即可覆盖旧值: (old, new) -> new
  • 只需返回旧值即可保留旧值: (old, new) -> old
  • 以某种方式合并两者,例如: (old, new) -> old + new
  • 甚至删除旧值: (old, new) -> null

如您所见,merge()它具有多种用途。那么我们的学术问题看起来如何merge()呢?非常令人愉快:

words.forEach(word->
        map.merge(word,1,(prev,one)-> prev + one)
);

您可以按以下方式阅读:如果不存在,请放在密钥1word,否则请添加1到现有值。我将参数之一命名为“ one”,因为在我们的示例中,该参数始终为…。1.遗憾地remappingFunction采用了两个参数,其中第二个是我们即将更新(插入或更新)的值。从技术上讲,我们已经知道此值,因此(word, 1, prev -> prev + 1)更容易消化。但是没有这样的API。

可以,但是merge() 真的有用吗?假设您有一个帐户操作(省略了构造函数,getter和其他有用的属性):

类别操作{
    私有最终String accNo;
    私人最终BigDecimal金额;
}

以及针对不同帐户的一系列操作:

var操作= List.of(
    new Operation(“ 123”,new BigDecimal(“ 10”)),
    新操作(“ 456”,新BigDecimal(“ 1200”)),
    new Operation(“ 123”,new BigDecimal(“-4”)),
    new Operation(“ 123”,new BigDecimal(“ 8”)),
    新操作(“ 456”,新BigDecimal(“ 800”)),
    新操作(“ 456”,新BigDecimal(“-1500”)),
    new Operation(“ 123”,new BigDecimal(“ 2”)),
    new Operation(“ 123”,new BigDecimal(“-6.5”)),
    新操作(“ 456”,新BigDecimal(“-600”))
);

我们想计算每个帐户的余额(总金额超过运营金额)。没有merge()这个就很麻烦:

var balances = new HashMap <String,BigDecimal>();

Operations.forEach(op-> {
    var key = op.getAccNo();
    balances.putIfAbsent(key,BigDecimal.ZERO);
    balances.computeIfPresent(key,(accNo,prev)-> prev.add(op.getAmount()));
});

但是有了以下帮助merge()

Operations.forEach(op->
        balances.merge(op.getAccNo(),op.getAmount(), 
                (soFar,金额)-> soFar.add(金额))
);

您在这里看到方法参考机会吗?

Operations.forEach(op->
        balances.merge(op.getAccNo(),op.getAmount(),BigDecimal ::添加)
);

我觉得这很可读。对于addamount定的每个操作accNo。结果符合预期:

{123=9.5, 456=-100}

ConcurrentHashMap

Map.merge()当您意识到它已在中正确实现时,它会更加明亮ConcurrentHashMap。这意味着我们可以原子地执行插入或更新操作。单行和线程安全。ConcurrentHashMap显然是线程安全的,但不能跨多个操作使用,例如get()then put()但是merge(),请确保没有更新丢失。

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注