Java 8-Optional.flatMap和Optional.map之间的区别


162

这两种方法有什么区别:Optional.flatMap()Optional.map()

一个例子将不胜感激。



5
@AlexisC。您的链接是关于Stream的地图和flatMap的,而不是可选的。
伊兰2015年

1
@Eran没关系,如果您了解map / flatMap的工作原理(无论是否适用于Stream),对于Optional都是一样的。如果操作员了解它对于Stream的工作原理,那么他就不应该问这个问题。概念是相同的。
Alexis C.

2
@AlexisC。并不是的。Optional的flatMap与Stream的flatMap几乎没有共同点。
伊兰2015年

1
@Eran我在说地图和flatMap之间的概念差异,我不是在Stream#flatMap和之间进行一对一的对应Optional#flatMap
Alexis C.

Answers:


166

使用map如果函数返回的对象,你需要或者flatMap如果该函数返回的Optional。例如:

public static void main(String[] args) {
  Optional<String> s = Optional.of("input");
  System.out.println(s.map(Test::getOutput));
  System.out.println(s.flatMap(Test::getOutputOpt));
}

static String getOutput(String input) {
  return input == null ? null : "output for " + input;
}

static Optional<String> getOutputOpt(String input) {
  return input == null ? Optional.empty() : Optional.of("output for " + input);
}

两个打印语句都打印相同的内容。


5
问题:是否会[flat]Map使用input == null?调用映射函数?我的理解是,Optional如果缺少分切符-[JavaDoc](docs.oracle.com/javase/8/docs/api/java/util/…)似乎可以支持这一点-“ 如果存在值,则应用.. 。 ”。
蜘蛛鲍里斯(Boris)

1
@BoristheSpider Optional.of(null)!= Optional.empty()
Diego Martinoia

14
@DiegoMartinoia Optional.of(null)是一个ExceptionOptional.ofNullable(null) == Optional.empty()
蜘蛛鲍里斯(Boris the Spider)

1
@BoristheSpider是的,你是对的。我试图回答您的问题,但我想我变得更加不清楚:从概念上讲,Optional.ofNullable(null)不应为空,但实际上它被认为是空的,因此不会执行map / flatmap。
迭戈·马蒂诺亚

1
我认为在getOutputOpt或getOutput中输入都不应为null
DanyalBurke

55

它们都具有从可选类型到某种功能的功能。

map()将函数“ 按原样 ”应用到您具有的可选组件上:

if (optional.isEmpty()) return Optional.empty();
else return Optional.of(f(optional.get()));

如果您的函数是的函数,会发生什么T -> Optional<U>
您的结果现在是Optional<Optional<U>>

就是这样flatMap():如果您的函数已经返回OptionalflatMap()则会更聪明,并且不会对其进行二次包装,而返回Optional<U>

它是两个功能性习语的组成:mapflatten


7

注意:-以下是地图和平面地图功能的说明,否则Optional主要设计为仅用作返回类型。

您可能已经知道Optional是一种可能包含或不包含单个对象的容器,因此可以在预期空值的任何地方使用它(正确使用Optional可能不会看到NPE)。例如,如果您有一个期望一个Person对象可能为空的方法,那么您可能想要编写如下方法:

void doSome(Optional<Person> person){
  /*and here you want to retrieve some property phone out of person
    you may write something like this:
  */
  Optional<String> phone = person.map((p)->p.getPhone());
  phone.ifPresent((ph)->dial(ph));
}
class Person{
  private String phone;
  //setter, getters
}

在这里,您返回了一个String类型,该类型将自动包装为Optional类型。

如果人员分类如下所示,即电话也是可选的

class Person{
  private Optional<String> phone;
  //setter,getter
}

在这种情况下,调用map函数会将返回的值包装在Optional中,并产生如下结果:

Optional<Optional<String>> 
//And you may want Optional<String> instead, here comes flatMap

void doSome(Optional<Person> person){
  Optional<String> phone = person.flatMap((p)->p.getPhone());
  phone.ifPresent((ph)->dial(ph));
}

PS; 除非没有isPresent()对其进行检查,否则切勿在Optional上调用get方法(如果需要),除非没有NullPointerExceptions无法生存。


1
我认为此示例可能会分散您答案的性质,因为您的课程使用Person不当OptionalOptional像这样在成员上使用API​​的意图是违反的-请参阅mail.openjdk.java.net/pipermail/jdk8-dev/2013-September/…–
8bitjunkie

@ 8bitjunkie感谢您指出,它与Scala的选择不同
。– SandeepGodara

6

帮我看看这两个函数的源代码。

地图 -包装结果在可选。

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Optional.ofNullable(mapper.apply(value)); //<--- wraps in an optional
    }
}

flatMap-返回“原始”对象

public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Objects.requireNonNull(mapper.apply(value)); //<---  returns 'raw' object
    }
}

1
flatMap“返回原始对象” 是什么意思?flatMap还会在中返回映射的对象“包装” Optional。区别在于,在的情况下flatMap,mapper函数将映射的对象包装在中,Optionalmap本身将对象包装在中Optional
德里克·马哈尔

@DerekMahar删除了我的,无需重新发布,因为您已正确编辑了评论。
maxxyme

3
  • Optional.map()

接受每个元素,如果值存在,则将其传递给函数:

Optional<T> optionalValue = ...;
Optional<Boolean> added = optionalValue.map(results::add);

现在添加的值是以下三个值之一:truefalse包装到Optional(如果optionalValue存在)中,或者包装为空的Optional

如果您不需要处理结果ifPresent(),则可以简单地使用,它没有返回值:

optionalValue.ifPresent(results::add); 
  • Optional.flatMap()

工作原理与相同的流方法相似。展平流。不同之处在于,如果显示值,则将其应用于功能。否则,将返回一个空的可选。

您可以使用它来构成可选的值函数调用。

假设我们有方法:

public static Optional<Double> inverse(Double x) {
    return x == 0 ? Optional.empty() : Optional.of(1 / x);
}

public static Optional<Double> squareRoot(Double x) {
    return x < 0 ? Optional.empty() : Optional.of(Math.sqrt(x));
}

然后,您可以计算逆的平方根,例如:

Optional<Double> result = inverse(-4.0).flatMap(MyMath::squareRoot);

或者,如果您愿意:

Optional<Double> result = Optional.of(-4.0).flatMap(MyMath::inverse).flatMap(MyMath::squareRoot);

如果inverse()squareRoot()返回Optional.empty(),则结果为空。


1
这不会编译。这两个表达式都返回Optional <Double>,而不是将结果分配给其的Double。
JL_SO

@JL_SO你是对的。因为逆具有Optional<Double>类型作为返回类型。
nazar_art

3

好的。你只有当你面对嵌套选配需要使用“flatMap”。这是例子。

public class Person {

    private Optional<Car> optionalCar;

    public Optional<Car> getOptionalCar() {
        return optionalCar;
    }
}

public class Car {

    private Optional<Insurance> optionalInsurance;

    public Optional<Insurance> getOptionalInsurance() {
        return optionalInsurance;
    }
}

public class Insurance {

    private String name;

    public String getName() {
        return name;
    }

}

public class Test {

    // map cannot deal with nested Optionals
    public Optional<String> getCarInsuranceName(Person person) {
        return person.getOptionalCar()
                .map(Car::getOptionalInsurance) // ① leads to a Optional<Optional<Insurance>
                .map(Insurance::getName);       // ②
    }

}

与Stream一样,Optional#map将返回一个由Optional包装的值。这就是为什么我们得到一个嵌套的Optional-的原因Optional<Optional<Insurance>。在②,我们想将其映射为保险实例,这就是悲剧发生的方式。根是嵌套的Optionals。如果我们能够获得核心价值,而不管外壳如何,那么我们就完成它。这就是flatMap所做的。

public Optional<String> getCarInsuranceName(Person person) {
    return person.getOptionalCar()
                 .flatMap(Car::getOptionalInsurance)
                 .map(Insurance::getName);
}

最后,如果您想系统地学习Java8 ,我就向您推荐Java 8 In Action

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.