映射 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 邮件列表中讨论了是否包含等同于 getOrDefaultSupplier。 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&lt;V&gt; 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的主要内容,如果未能解决你的问题,请参考以下文章

Map.getOrDefault()方法

由于 `HashMap.getOrDefault()` 函数中的接收器类型不匹配,以下候选均不适用

给定带有 getOrDefault 的键,更新 hashmap 值

getOrDefault等jdk8为hash map 新增方法

javajava getOrDefault 方法的一个坑,容易导致OOM

csharp [GetOrDefault]键がなければ默认値を返します。#ExtensionMethod