使用Optional进行Null安全方法链接

时间:2013-05-31 22:35:41

标签: java guava

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

这个问题与Guava Optional type, when transformation returns another Optional有关,{{3}}基本上是同一个问题,但对于一个不同的用例,我认为这可能不是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>>

transform()上使用类似Optional的方法来接受返回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课程)。我也很想听到番石榴团队的消息,或者指出与该问题相关的讨论(我在搜索中没有发现任何内容)。

2 个答案:

答案 0 :(得分:12)

你正在寻找一些Monad,但是Guava的可选(与Scala的选项相反)只是一个Functor。

Functor到底是什么?!

Functor和Monad是一种盒子,包含一些价值的上下文。 包含一些A类值的Functor知道如何应用函数A =&gt; B并将结果放回Functor中。例如:从Optional中获取内容,转换并回送到Optional中。 在函数式编程语言中,这种方法通常被命名为“map&#39;”。

莫娜......什么?

Monad与Functor几乎相同,只是它消耗Monad中包含的函数返回值(A =&gt; Monad,例如Int =&gt; Optional)。 这种神奇的Monad方法通常被称为“flatMap&#39;。

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

Functors&amp; 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的项目,昨天我写了一些帮助类,名为&#39; Optionals&#39;,这为我节省了很多时间。

它提供了一些帮助方法,允许我将Optional变成Monads(flatMap)。

以下是代码:https://gist.github.com/mkubala/046ae20946411f80ac52

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

为什么将结果提升为Optional? 为了避免传递给transform的函数可能返回null并且整个表达式返回&#34;出现null&#34; (由于这个后置条件 - &gt;参见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);

答案 1 :(得分:0)

您可能已经想到了这一点,但是您可以在每次转换后添加.or(Optional.absent) Optional(在.transform(Book.getPublisher之后的情况下)将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);

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

相关问题