我想在java中使用一种方法,它允许我修改一个值(如果存在),或者如果它不存在则插入一个。与merge类似,但是:
我不得不写这个。自己编写的问题是并发映射的版本并不简单
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;
}
答案 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);
}
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
替换它。