映射 getOrDefault VS getOrUseSupplier
Posted
技术标签:
【中文标题】映射 getOrDefault VS getOrUseSupplier【英文标题】:Map getOrDefault VS getOrUseSupplier 【发布时间】:2016-08-13 00:58:44 【问题描述】:我开始学习 lambdas,但我不明白为什么 java Map 有:
getOrDefault(Object key, V defaultValue)
而不是(工作方式相同,但如果 value 不存在,则 defaultValue 将从供应商处获取):
getOrUseSupplier(Object key, Supplier<V> defaultValue)
我目前看到的当前解决方案的优势:
defaultValue 不必是最终/有效的最终值 看起来更简单,无需了解 lambda 语法缺点:
如果在使用 getOrDefault 时我们创建了新对象,它将被创建并立即传递给 GC(使用供应商时,它根本不会被创建)。我想知道使用 & 拥有 getOrDefault 而不是 getOrUseSupplier 是否还有更多缺点。你能告诉我java库中是否有这样的方法:
static <V> V getOrUseSupplier(Map<?, V> map, Object key, Supplier<V> supplier)
尝试从地图中获取价值,如果不存在则从供应商那里获取价值。
【问题讨论】:
在getOrUserSupplier
的情况下,您仍然会创建一个必须进行GC 处理的Supplier
对象。因此,与getOrDefault
相比,您节省的唯一成本是运行V
的构造函数(如果成本很高的话)。
你能告诉我每次调用方法时创建供应商需要多少内存吗?
@sepp2k 它真的会创建一个新的供应商吗?是不是应该编译成静态方法调用,也就是说只创建一个供应商实例?
@sepp2k 好的,我找到了答案。不捕获任何变量的 Lambda 只创建一次,并且不会为每次使用占用内存。捕获一些外部变量的 Lambda 每次调用都会占用一些内存(我得到 24 个字节的 lambda 和 1 个捕获的变量),直到 JIT 处理它。
【参考方案1】:
我猜是因为没有炸毁地图界面。您可以使用Optional.orElseGet(Supplier)
作为解决方法(如果您不在地图中保留空值):
Optional.ofNullable(map.get(key)).orElseGet(supplier)
【讨论】:
【参考方案2】:Map
中与 getOrUseSupplier()
最接近的等价物命名为 computeIfAbsent()
,它允许使用键计算值,比仅使用 Supplier
提供更大的灵活性。与getOrDefault
不同,它还将计算值存储在Map
中。这是因为它们具有不同的用例并且并不真正相关。虽然getOrDefault
通常用于返回一个“安全的”非空默认值(例如返回空字符串而不是空值),表明地图中应该有某些东西应该,computeIfAbsent()
意味着某些东西必须在地图中,如果不是,则需要创建它,否则程序的内部状态不正确。
以下示例忽略了键,只使用了供应商的值。
public static <V,T> V getOrUseSupplier(Map<T, V> map, T key, Supplier<V> supplier)
return map.computeIfAbsent(key, k -> supplier.get());
【讨论】:
请注意computeIfAbsent
将计算值输入到映射中,而getOrDefault
没有副作用。
是的,这是不等效的,因为getOrDefault
不会更改地图,而computeIfAbsent
会。
@ChristofferHammarström 已编辑以包含差异。【参考方案3】:
在设计 lambda 时,lambda-dev 邮件列表中讨论了是否包含等同于 getOrDefault
的 Supplier
。 Oracle 的 Brian Goetz 给出了将其从 API 中排除的两个原因:避免不具有自身重要性的特性的特性蔓延,以及 lambda 捕获的成本。
Question
有时构建默认对象的成本太高,依赖于 三元操作又是逃生舱口。一种 Map.getOrDefault(Object,Supplier) 会解决这个问题。
之前有考虑过吗?
Feature creep
功能蠕变警报!
getOrDefault 勉强,我的意思是勉强承担了它的重量。
Cost
顺便说一句,我们不愿意让供应商高兴的另一个原因是 是 lambda 的实际成本模型和感知成本模型之间的不匹配 捕获。
捕获无状态(非捕获)lambda 基本上是免费的。
捕获有状态的,例如
() -> state.toString()
目前的成本与实例化内部类实例相当 捕获一个局部变量。由于提供的唯一原因 供应商版本是成本,即使它可能会带来巨大的成本 减少,它可能不会像人们期望的那样提供那么多。所以这 是一个小小的考虑,有利于不要过度依赖这个技巧。
我们正在研究能够显着降低这些成本的虚拟机工作, 但首先我们必须让 8 个出门。
【讨论】:
感谢 Evgeny Kharitonov 找到此邮件列表讨论:***.com/a/64149972/1108305【参考方案4】:Objects.requireNonNullElseGet()
是实现所需getOrUseSupplier(Object key, Supplier<V> defaultValueSupplier)
行为的好方法。
Objects.requireNonNullElseGet(map.get(key), defaultValueSupplier));
优势(与其他解决方案相比):
单行 对地图没有副作用 不创建任何垃圾对象(如 Optional) 核心 java(不需要库)需要 Java 9。
我很高兴在地图本身上也有这种方便的方法getOrUseSupplier()
。
也许在即将发布的版本中......
UPD
我认为this mail from Brian Goetz 是您原始问题的官方答案。 Java 设计者担心程序员会滥用这种方法来捕获 lambda。
【讨论】:
通过邮件列表对话很好地找到了!我已经扩展了它自己的完整答案。【参考方案5】:当使用getOrDefault()
方法时,你应该记住,如果你的第二个参数(默认值)由于某种原因抛出异常,即使键存在于映射中,你也会抛出异常,作为默认值无论键是否存在,都会评估值。这是一个例子:
private static final Map<String, Integer> RANKS = Map.of(
"A", 9,
"B", 8,
"C", 7,
"D", 6
);
public static void main(final String[] args)
final int value1 = RANKS.getOrDefault("E", 5); // This will work
final int value2 = RANKS.getOrDefault("A", Integer.valueOf("G")); // Exception!
正如您所见,即使键 A
存在,它仍然会尝试评估默认值,在此示例中它本身会抛出 NumberFormatException
。
如果你使用那个假设的getOrUseSupplier()
方法(不幸的是,正如@Kayaman 已经解释的那样,它在Java 中还不存在,你应该自己实现它),你可以这样做:
final int value = getOrUseSupplier(RANKS, "A", () -> Integer.parseInt("G")); // This will work
现在只有当密钥A
丢失时才会抛出异常。
【讨论】:
以上是关于映射 getOrDefault VS getOrUseSupplier的主要内容,如果未能解决你的问题,请参考以下文章
由于 `HashMap.getOrDefault()` 函数中的接收器类型不匹配,以下候选均不适用
给定带有 getOrDefault 的键,更新 hashmap 值
getOrDefault等jdk8为hash map 新增方法