【问题标题】:Null-safe method chaining with Optional使用 Optional 的空安全方法链接
【发布时间】:2013-05-27 20:21:02
【问题描述】:

Guava 的Optional 模式很棒,因为它有助于消除 null 的歧义。当链的第一部分可能不存在时,transform 方法对于创建空安全方法链非常有帮助,但在链的其他部分不存在时则无用。

这个问题与Guava Optional type, when transformation returns another Optional 相关,它提出了基本相同的问题,但针对不同的用例,我认为这可能不是Optional 的预期用途(处理错误)。

考虑一个方法Optional<Book> findBook(String id)findBook(id).transform(Book.getName) 按预期工作。如果没有找到书,我们会得到Absent<String>,如果找到书,我们会得到Present<String>

在中间方法可能返回null/absent() 的常见情况下,似乎没有一种优雅的方式来链接调用。例如,假设Book 有一个方法Optional<Publisher> getPublisher(),我们想获取一本书的出版商出版的所有书籍。自然语法似乎是findBook(id).transform(Book.getPublisher).transform(Publisher.getPublishedBooks),但这会失败,因为transform(Publisher.getPublishedBooks) 调用实际上会返回Optional<Optional<Publisher>>

Optional 上使用类似transform() 的方法来接受返回Optional 的函数似乎相当合理。它的行为与当前实现完全相同,只是它不会将函数的结果包装在 Optional 中。实现(对于Present)可能是:

public abstract <V> Optional<V> optionalTransform(Function<? super T, Optional<V>> function) {
    return function.apply(reference);
}

Absent 的实现与 transform 保持不变:

public abstract <V> Optional<V> optionalTransform(Function<? super T, Optional<V>> function) {
    checkNotNull(function);
    return Optional.absent();
}

如果有一种方法可以处理返回null 而不是Optional 用于处理遗留对象的方法,那就太好了。这样的方法类似于transform,但只需在函数的结果上调用Optional.fromNullable

我很好奇是否有其他人遇到过这种烦恼并找到了很好的解决方法(不涉及编写自己的 Optional 类)。我也很想收到 Guava 团队的来信,或者被指向与该问题相关的讨论(我在搜索中没有找到任何内容)。

【问题讨论】:

  • 你试过discussion group吗?考虑在那里发帖,即使只是为了指出这个(写得很好的)问题。

标签: java guava


【解决方案1】:

您正在寻找一些 Monad,但 Guava 的 Optional(与例如 Scala 的 Option 相反)只是一个 Functor。

Functor 到底是什么?!

Functor 和 Monad 是一种盒子,一种包装一些值的上下文。 包含一些 A 类型值的 Functor 知道如何应用函数 A => B 并将结果放回 Functor。例如:从 Optional 中取出一些东西,变换,然后回绕成 Optional。 在函数式编程语言中,这种方法通常被命名为“map”。

莫娜..什么?

Monad 几乎和 Functor 一样,只是它使用 Monad 中包装的函数返回值(A => Monad,例如 Int => Optional)。 这种神奇的 Monad 方法通常被称为“flatMap”。

在这里您可以找到关于基本 FP 术语的非常棒的解释:http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html

Functors & Monads 来了!

Java 8 中的可选可分为 Functor (http://docs.oracle.com/javase/8/docs/api/java/util/Optional.html#map-java.util.function.Function-) 和 Monad (http://docs.oracle.com/javase/8/docs/api/java/util/Optional.html#flatMap-java.util.function.Function-)。

很好的 mon(ad)olog,Marcin,但我该如何解决我的特殊问题?

我目前正在开发一个使用 Java 6 的项目,昨天我编写了一些名为“Optionals”的帮助程序类,这为我节省了很多时间。

它提供了一些辅助方法,允许我将 Optional 转换为 Monads (flatMap)。

这里是代码:https://gist.github.com/mkubala/046ae20946411f80ac52

因为我项目的代码库仍然使用空值作为返回值,所以我引入了 Optionals.lift(Function),它可以用来将结果包装到 Optional 中。

为什么将结果提升到 Optional 中? 为了避免传递给转换的函数可能返回 null 并且整个表达式将返回“null 的存在”的情况(顺便说一句,这在 Guava 的 Optional 中是不可能的,因为这个后置条件 -> 参见https://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/base/Present.java?r=0823847e96b1d082e94f06327cf218e418fe2228#71 的第 71 行)。

几个例子

假设 findEntity() 返回一个 Optional 并且 Entity.getDecimalField(..) 可能返回 BigDecimal 或 null:

Optional<BigDecimal> maybeDecimalValue = Optionals.flatMap(
    findEntity(),
    new Function<Entity, Optional<BigDecimal>> () {
        @Override 
        public Optional<BigDecimal> apply(Entity input) {
            return Optional.fromNullable(input.getDecimalField(..));
        }
    }
);

再一个例子,假设我已经有一些函数,它从实体中提取十进制值,并且可能返回空值:

Function<Entity, Decimal> extractDecimal = .. // extracts decimal value or null
Optional<BigDecimal> maybeDecimalValue = Optionals.flatMap(
    findEntity(),
    Optionals.lift(extractDecimal)
);

最后但并非最不重要 - 以您的用例为例:

Optional<Publisher> maybePublisher = Optionals.flatMap(findBook(id), Optionals.lift(Book.getPublisher));

// Assuming that getPublishedBooks may return null..
Optional<List<Book>> maybePublishedBooks = Optionals.flatMap(maybePublisher, Optionals.lift(Publisher.getPublishedBooks));

// ..or simpler, in case when getPublishedBooks never returns null
Optional<List<Book>> maybePublishedBooks2 = maybePublisher.transform(Publisher.getPublishedBooks);

// as a one-liner:
Optionals.flatMap(maybePublisher, Optionals.lift(Publisher.getPublishedBooks)).transform(Publisher.getPublishedBooks);

【讨论】:

    【解决方案2】:

    您可能已经猜到了,但是您可以在每个返回 Optional 的转换之后添加 .or(Optional.absent)(在您的情况下是在 .transform(Book.getPublisher 之后)将 Optional&lt;Optional&lt;T&gt;&gt; 减少到 Optional&lt;T&gt;

    Optional<List<Book>> publishedBooks = findBook(id).transform(Book.getPublisher).
            or(Optional.absent()).transform(Publisher.getPublishedBooks);
    

    可惜这里无法推断Optional.absent的类型,所以代码实际上变成了:

    Optional<List<Book>> publishedBooks = book.transform(Book.getPublisher).
            or(Optional.<Publisher> absent()).transform(Publisher.getPublishedBoooks);
    

    不太方便,不过好像没有别的办法了。

    【讨论】:

      猜你喜欢
      • 2015-09-19
      • 1970-01-01
      • 2012-02-24
      • 2012-11-11
      • 1970-01-01
      • 2021-07-12
      • 1970-01-01
      • 1970-01-01
      • 2011-09-10
      相关资源
      最近更新 更多