空安全方法链接,可选

2022-09-04 03:34:36

番石榴的模式很棒,因为它有助于消除空的歧义。当链的第一部分可能不存在时,该方法对于创建空安全的方法链非常有用,但是当链的其他部分不存在时,此方法没有用处。Optionaltransform

这个问题与Guava Optional类型有关,当转换返回另一个Optant时,它问的问题基本上是相同的,但对于不同的用例,我认为可能不是预期的用途(处理错误)。Optional

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

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

有一个类似 -的方法似乎相当合理,该方法将接受返回 .它的行为与当前实现完全相同,只是它不会将函数的结果包装在 Optional 中。(for ) 的实现可能为:transform()OptionalOptionalPresent

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

的实现与 以下相同:Absenttransform

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

如果有一种方法可以处理返回的方法,而不是处理遗留对象,那也会很好。这样的方法就像只是简单地调用函数的结果。nullOptionaltransformOptional.fromNullable

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


答案 1

你正在寻找一些Monad,但Guava的Optable(与Scala的Option相反)只是一个函子。

函子到底是什么?!

Functor 和 Monad 是一种盒子,一个包装一些值的上下文。包含某种类型 A 值的函子知道如何应用函数 A = > B 并将结果放回函子中。例如:从“可选”中获取某些内容,转换并包装回“可选”。在函数式编程语言中,这种方法通常被命名为“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”的帮助器类,这为我节省了很多时间。

它提供了一些帮助器方法,使我能够将Optimed转换为Monads(flatMap)。

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

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

为什么将提升结果设置为可选?为了避免将函数传递到转换中可能返回null并且整个表达式将返回“null的呈现”的情况(顺便说一句,这在Guava的Austable中是不可能的,因为这个后置条件->请参阅 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

您可能已经想通了,但是您可以在每次返回的转换之后添加(在您的情况下)简化为:.or(Optional.absent)Optional.transform(Book.getPublisherOptional<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);

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


推荐