问题 重新设计使用带有抛出异常的Java 8 Map.computeIfAbsent()的方法


我正在尝试重做我的一些方法,使它们使用Java 8更加简洁,我试图慢慢吸收它的新功能。

这是一种添加方法的方法 value 到了 Map<Key, Set<Value>>。有三个可能性:

  1. 密钥不存在:添加密钥并将包含该值的新集合与其关联。
  2. 密钥存在:该值将添加到现有集合中。请注意,一组永远不会 null因为我有一些先决条件来解决这个问题。
  3. 密钥存在且值已包含在集合中:a IllegalArgumentException 被抛出。

实现此行为的代码如下,并且它不使用Java 8功能:

public void addValue(Key key, Value value) {
    // irrelevant preconditions...

    Set<Value> valuesForKey = myMap.get(key);
    if (valuesForKey != null && valuesForKey.contains(value))
        throw new IllegalArgumentException("Association exists already");

    if (valuesForKey == null)
        myMap.put(key, new HashSet<Value>(Arrays.asList(value)));
    else
        valuesForKey.add(value);
}

我想使用Java 8方法缩短此代码 computeIfAbsent

我可以总结一下 if-else 阻止,但我无法通过设置的值的冗余 key 映射到执行前置条件检查时已经被检索。

public void addValue(Key key, Value value) {
    // irrelevant preconditions...

    Set<Value> valuesForKey = myMap.get(key);
    if (valuesForKey != null && valuesForKey.contains(value))
        throw new IllegalArgumentException("Association exists already");

    myMap.computeIfAbsent(key, v -> new HashSet<Value>()).add(value);
}

无论如何我可以在一条指令中合并所有这些吗?


8705
2018-05-17 13:38


起源

虽然你当然可以这样做,但你应该考虑使用 Multimap (例如来自番石榴)。有一个 Map<K,Set<V>> 要么 Map<K,List<V>>在99%的用例中都有一种设计气味。 - Landei
@Landei,在我看来,它并不是真正的“代码味道”,因为JDK没有多重集,而且可能不想仅仅为了那个用途而引入整个库。这是在这些情况和CHM中可用的最简单的解决方案 compute* 和 merge 方法使这些事情变得相当直接。 - the8472
在我看来,JDK集合处于可怜状态(例如缺少可用的不可变集合),并且我几乎所有项目都使用像Guava这样的库。我的经验是,一旦你导入了这样的lib,你会发现越来越多的地方有用。 - Landei
@Landei我正在阅读这个“老”问题,现在我必须问,为什么你说这是一个糟糕的设计?在我的项目恕我直言,使用一个绝对意义 Map<K, Set<V>>。为简洁起见,我不想详细说明,但请说明为什么这是一个糟糕的设计:/ - dabadaba
@dabadaba如果集合确实是模型中的“值”(例如,在键下找到一个空集不同于找不到任何东西),那么在1%的情况下,你就是正确的。但是如果你只想在一个键下允许多个值,它就不再是一个Map,而是一个Multimap,它具有不同的行为(使用Map时需要“模拟”)。 - Landei


答案:


你可以利用这个事实 add(element) 方法将返回 true 如果集合不包含指定的元素。如果此调用返回 false,这意味着该元素未被添加,因为它已经存在。因此,您可以使用:

public void addValue(Key key, Value value) {
    boolean added = myMap.computeIfAbsent(key, k -> new HashSet<>()).add(value);
    if (!added) {
        throw new IllegalArgumentException("Association exists already");
    }
}

11
2018-05-17 13:44



你不会以这种方式得到真正的底层异常。坦率地说,我不知道为什么计算机不能抛出异常。没有理由说它为什么不能。它不在单独的线程中运行。 - momo
@momomo为什么会这样 computeIfAbsent 抛出异常?将元素添加到已存在的集合不会引发异常。 - Tunaki
也许在添加之前你需要做一些事情,这可能会引发异常 - momo
@momomo听起来像不应该存在的代码 addValue 方法然后,但在调用之前。 - Tunaki
计算需要以同时安全的方式完成。但无论如何,您可以抛出一个包含在运行时异常中的异常。在addvalue中,还可以声明一个异常并捕获,之后您可以看到返回值是否为true以及是否存在异常... - momo