使用Optional实现空安全的方法链式调用

15
Guava的Optional模式非常好,因为它有助于消除null的歧义。当链的第一部分可能不存在但其他部分不存在时,transform方法非常有用,但在其他部分不存在时并不实用。
这个问题涉及到Guava Optional类型,当转换返回另一个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团队的意见,或者指向与此问题相关的讨论(在我的搜索中没有找到)。

你尝试过讨论组了吗?即使只是为了指向这个(写得很好的)问题,也可以考虑在那里发布。 - Paul Bellora
2个回答

12
您正在寻找一些 Monad ,但是 Guava 的 Optional(与例如 Scala 的 Option 相反)只是一个 Functor 。
到底什么是 Functor?
Functor和Monad都是一种容器,用于包装某些值的上下文。 包含类型为A的某个值的Functor知道如何应用函数A => B并将结果放回Functor。 例如:从Optional中获取一些内容,进行转换,并将其重新包装回Optional。在函数式编程语言中,这种方法通常称为“map”。
Monad与Functor几乎相同,除了它消耗了返回包装在Monad中的值的函数(例如Int => Optional)之外。 这种神奇的Monad方法通常称为“flatMap”。
您可以在此处找到有关基本FP术语的真正出色的解释:http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html Functors和Monads即将来临!
Java 8中的Optional可以分类为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-)。
不错的单(ad)论,Marcin,但我该如何解决我的特定问题?
我目前正在开发使用Java 6的项目,并且昨天编写了一些帮助类,称为“Optionals”,这为我节省了很多时间。
它提供了一些辅助方法,允许我将Optional转换为Monad(flatMap)。
以下是代码:https://gist.github.com/mkubala/046ae20946411f80ac52 因为我的项目代码库仍然使用null作为返回值,所以我引入了Optionals.lift(Function),它可用于将结果包装到Optional中。
为什么要将结果提升为Optional?
为了避免传递到转换函数的函数可能返回null并且整个表达式将返回“null的存在”(顺便说一下,由于此后置条件->请参见https://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/base/Present.java?r=0823847e96b1d082e94f06327cf218e418fe2228#71第71行,这在Guava的Optional中是不可能的)。
一些例子
假设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);

0

你可能已经明白了,但是在每个返回Optional的转换之后(在你的情况下,在.transform(Book.getPublisher)之后),你可以添加.or(Optional.absent),将Optional<Optional<T>>缩减为Optional<T>

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);

不太方便,但似乎没有其他办法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接