【问题标题】:using Java8's Optional<T> in a functional way for updating source with default value以功能方式使用 Java8 的 Optional<T> 来使用默认值更新源
【发布时间】:2015-07-14 06:06:59
【问题描述】:

这可能更多是关于函数式编程的问题,而不是专门针对 Java 8 的问题,但这是我现在正在使用的。

我有一个源对象(可以代表一个存储库,或一个会话...,在这里无关紧要),它有一个方法retrieveSomething(),它返回一个Optional&lt;SomethingA&gt;。 我在某处有一个返回Something 的方法,通过调用retrieveSomething() 并在可选为空的情况下提供默认值,如下所示:

return source.retrieveSomething()
             .orElseGet(() -> provideDefaultValue());

现在我想修改此代码,以便在源不包含任何值的情况下(因此可选为空),源将更新为提供的默认值。

当然,我可以在 lambda 表达式代码块中轻松做到这一点:

return source.retrieveSomething()
             .orElseGet(() -> {
                     Something sth = provideDefaultValue()
                     source.putSomething(sth);
                     return sth;
                 });

但如果我理解正确的话,我不应该使用会导致副作用的函数。那么执行此操作的“正确”(功能性)方法是什么,保持使用Optional 的好处(在实际代码中,我实际上也在对其执行map 操作,但这在这里无关紧要)?

【问题讨论】:

  • 等一下。您正在尝试更新Optional?从概念上讲,这没有任何意义。 Optional 要么存在,要么你提供一个新值来代替它。
  • 在这里定义“副作用”。目前尚不清楚您的输入是什么。
  • @Makoto 不,我不想更新Optional。我想更新Optional 的来源(可能是数据库、REST 服务、HTTP 会话……),以便下次调用retrieveSomething() 时,它不会返回空的@987654334 @。假设provideDefaultValue() 是一个非常繁重的计算。
  • @fge with "side effect" 我的意思是source.putSomething(sth) 将进行数据库调用、Web 服务调用或任何其他 IO 操作。
  • 更新源以包含默认值是一个副作用,不管你怎么做(除非可能涉及单子......)。没有副作用是没有办法的。

标签: java functional-programming java-8


【解决方案1】:

您可以按照 Java 的方式使用 Map.computeIfAbsent()

它接受第二个参数,它是一个关于如何计算和插入记录的函数:

所以你的代码会变成:

Something something = source.computeIfAbsent(sth, (k)->provideDefaultValue());

使用 lambda 计算默认值而不是仅将其传入的一个优点是,只有在需要时才会评估 lambda,因此如果计算默认值很昂贵,您只需在需要时付费.

【讨论】:

  • @herman 我知道你的source 没有实现Map 我的建议是给你的对象添加一个computeIfAbsent() 方法。不同之处在于您的 computeIfAbsent 可以成为原子
  • 哦,我现在明白了,您的意思就像只是将 Supplier&lt;Something&gt; 作为参数添加到 retrieveSomething() 方法并让源更新其值。这与 Makoto 的建议类似,我自己也简单地想到过,但是 1)我更喜欢服务中的方法只做一件事情,2)这实际上避免了使用 Optional,但我我对一个方法可能返回一个空的Optional 的情况感兴趣,并且除了返回一个默认值之外还需要发生一些事情,而不是诉诸命令式风格。所以让我们假设我无法更改source
  • 这个答案得到了最多的支持,似乎人们告诉我根本不要使用Optional。但是,返回值正是 Optional 的用途。
  • @herman: Optional查询 的理想返回值,并不意味着修改原始数据源。
  • @herman:这个答案并没有告诉你避免Optional。它仅在缺少值触发创建的用例中已过时,因此始终存在现值。您的来源可能仍然有一个返回 Optional 的纯查询方法。
【解决方案2】:

从概念的角度来看,您使用deal with the absence of a return value 的可选模式。这意味着,如果您的 source 实例不包含供您使用的值,您可以选择提供一个默认值来代替它。

不建议直接修改Optional 以提供其值;该实例可能是暂时的,并且在后续调用检索它时会有所不同。

由于函数调用真正控制了 Optional 返回的内容,如果您真的想沿着这条路线走,唯一的方法是修改该值的计算方式。这真的应该在函数内完成提供Optional,但如果需要也可以在它之外完成。

由于这里没有足够的代码结构来真正写一些例子,我将描述步骤:

  • 在提供Optional 的方法之外,您编写与之前相同的闭包,其副作用是调整用于计算原始Optional 的值。这可能是某种领域。

  • 在提供Optional 的方法内部,确保不会在其他任何地方公开provideDefaultValue(因为他们不需要它),并在打包Optional 之前使用boolean 条件.

    return value == null ? Optional.of(provideDefaultValue()) : Optional.of(value);
    

    ...但这确实违背了Optional 的目的,因为您仍在进行null 检查。

    对上述情况稍好一点的方法是计算value,使其要么是它本身,要么是默认值,然后返回Optional...

    value = computeValue();
    if(null == value) {
        value = provideDefaultValue();
    }
    return Optional.of(value);
    

    ...但又一次,严重违背了Optional 的目的,就像你正在做null 检查一样。

【讨论】:

  • 我不打算更新Optional 本身。 source 不是 Optionalsource 是例如一个在其retrieveSomething() 方法中从数据库或REST 服务中检索数据的对象。因此,如果数据库(或 REST 服务)没有数据并返回一个空的可选项,我想返回一个默认值,但也要使用该默认值更新数据库(或 REST 服务)。
  • 在这种情况下,您希望在服务内执行此操作(尽管如果您可以保证始终返回值,那么此时使用 Optional 是不明智的)。我上面的回答对此进行了详细说明。
【解决方案3】:

我自己想出的答案,我并不完全满意,但可以作为我正在寻找的一个例子:

我可以实现类似于Pair&lt;V1,V2&gt; 类的东西,然后将Optional&lt;Something&gt; 映射到Pair&lt;Something, Boolean&gt;,其中Boolean 值将指示该值是否是生成的默认值:

Pair<Something, Boolean> somethingMaybeDefault = 
    source.retrieveSomething()
          .map(sth -> new Pair<Something, Boolean>(sth, false))
          .orElseGet(() -> new Pair<Something, Boolean>(provideDefaultValue(), true));

如果布尔值为假,我会更新:

if (somethingMaybeDefault.value2()) {
    source.putSomething(somethingMaybeDefault.value1());
}

最后返回新值:

return somethingMaybeDefault.value1();

当然,这使用命令式的风格进行更新,但至少功能保持纯粹。 不过,我不确定这是最好的答案。

【讨论】:

  • 别太担心。无论如何,我们都在混合命令式和函数式。只要我们知道确切的执行语义就可以了。我们甚至可能会质疑纯 FP 的人是否有很好的副作用解决方案,或者仅仅是缓存;他们的回答对我们这些迫切需要的人来说可能看起来很怪诞。
  • @bayou.io 所以对于一般情况(假设我们不能更改sourceretrieveSomething() 方法自己进行更新,避免使用Optional),你认为我应该接受我自己的答案吗?
  • 我认为你应该只使用普通的旧 java :) 命令式代码分支从 source.retrieveSomething() 返回的可选项
猜你喜欢
  • 2017-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多