Java 8 - Diferencia entre Optional.flatMap y Optional.map

162

¿Cuál es la diferencia entre estos dos métodos: Optional.flatMap()y Optional.map()?

Un ejemplo sería apreciado.

codependiente
fuente
55
@AlexisC. Su enlace es sobre el mapa de Stream y flatMap, no Opcional.
Eran
1
@Eran Eso no importa, si comprende cómo funciona map / flatMap, ya sea para un Stream o no, es lo mismo para un Opcional. Si el operador entendió cómo funciona para un Stream, entonces no debería hacer esta pregunta. El concepto es el mismo.
Alexis C.
2
@AlexisC. Realmente no. FlatMap de Opcional tiene poco en común con flatMap de Stream.
Eran
1
@Eran Estoy hablando de la diferencia conceptual entre un mapa y un FlatMap, no estoy haciendo una correspondencia uno a uno entre Stream#flatMapy Optional#flatMap.
Alexis C.

Respuestas:

166

Use mapsi la función devuelve el objeto que necesita o flatMapsi la función devuelve un Optional. Por ejemplo:

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

Ambas declaraciones impresas imprimen lo mismo.

asilias
fuente
55
Pregunta: ¿ [flat]Mapalguna vez llamaría a la función de mapeo con un input == null? Tengo entendido que Optionalsortcuts si está ausente: el [JavaDoc] ( docs.oracle.com/javase/8/docs/api/java/util/… ) parece respaldar esto: " Si hay un valor presente, aplique ... . ".
Boris the Spider
1
@BoristheSpider Opcional.of (nulo)! = Opcional.empty ()
Diego Martinoia
14
@DiegoMartinoia Optional.of(null)es un Exception. Optional.ofNullable(null) == Optional.empty().
Boris the Spider
1
@BoristheSpider sí, tienes razón. Intenté responder a su pregunta, pero creo que lo hice aún más confuso: conceptualmente, Opcional. De Anulable (nulo) NO debe estar vacío, pero en la práctica se considera que está, y por lo tanto, map / flatmap no se ejecutan.
Diego Martinoia
1
Creo que la entrada nunca debería ser nula en getOutputOpt o getOutput
DanyalBurke
55

Ambos toman una función del tipo de opcional a algo.

map()aplica la función " tal cual " en el opcional que tiene:

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

¿Qué sucede si su función es una función de T -> Optional<U>?
Su resultado es ahora un Optional<Optional<U>>!

De eso flatMap()se trata: si su función ya devuelve un Optional, flatMap()es un poco más inteligente y no lo envuelve dos veces, regresando Optional<U>.

Es la composición de dos modismos funcionales: mapy flatten.

Diego Martinoia
fuente
7

Nota: a continuación se muestra la ilustración del mapa y la función de mapa plano; de lo contrario, Opcional está diseñado principalmente para usarse solo como un tipo de retorno.

Como ya sabrá, Opcional es un tipo de contenedor que puede contener o no un solo objeto, por lo que se puede usar siempre que anticipe un valor nulo (es posible que nunca vea NPE si usa Opcional correctamente). Por ejemplo, si tiene un método que espera que un objeto persona sea anulable, puede escribir el método de la siguiente manera:

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
}

Aquí ha devuelto un tipo de cadena que se ajusta automáticamente en un tipo opcional.

Si la clase de persona se ve así, es decir, el teléfono también es opcional

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

En este caso, invocar la función de mapa envolverá el valor devuelto en Opcional y producirá algo como:

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

PD; Nunca llame al método get (si es necesario) en un Opcional sin verificarlo con isPresent () a menos que no pueda vivir sin NullPointerExceptions.

SandeepGodara
fuente
1
Creo que es probable que este ejemplo distraiga la naturaleza de su respuesta porque su clase Personestá haciendo un mal uso Optional. Es en contra de la intención de la API usar Optionalen miembros como este: ver mail.openjdk.java.net/pipermail/jdk8-dev/2013-September/…
8bitjunkie
@ 8bitjunkie Gracias por señalarlo, difiere de la opción de Scala ...
SandeepGodara
6

Lo que me ayudó fue mirar el código fuente de las dos funciones.

Mapa : ajusta el resultado en un Opcional.

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 : devuelve el objeto 'sin procesar'

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
    }
}
Robert Niestroj
fuente
1
¿Qué quiere decir con flatMap"devuelve el objeto 'crudo'"? flatMaptambién devuelve el objeto mapeado "envuelto" en un Optional. La diferencia es que, en el caso de flatMap, la función de mapeador envuelve el objeto mapeado Optionalmientras que el mapmismo lo envuelve Optional.
Derek Mahar
@DerekMahar eliminó el mío, no es necesario volver a publicarlo, porque ha editado su comentario correctamente.
maxxyme
3
  • Optional.map():

Toma todos los elementos y si el valor existe, se pasa a la función:

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

Ahora agregado tiene uno de tres valores: trueo falseenvuelto en un Opcional , si optionalValueestaba presente, o un Opcional vacío de lo contrario.

Si no necesita procesar el resultado, simplemente puede usarlo ifPresent(), no tiene valor de retorno:

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

Funciona de manera similar al mismo método de secuencias. Aplana la corriente de corrientes. Con la diferencia de que si se presenta el valor, se aplica a la función. De lo contrario, se devuelve un opcional vacío.

Puede usarlo para componer llamadas de funciones de valor opcionales.

Supongamos que tenemos métodos:

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

Luego puedes calcular la raíz cuadrada de la inversa, como:

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

o, si lo prefieres:

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

Si el inverse()o el squareRoot()devuelve Optional.empty(), el resultado está vacío.

nazar_art
fuente
1
Esto no se compila. Ambas expresiones devuelven un Opcional <Doble> en lugar del Doble al que está asignando el resultado.
JL_SO
@JL_SO tienes razón. Porque inverso tiene Optional<Double>tipo como tipo de retorno.
nazar_art
3

Bueno. Usted sólo tendrá que utilizar 'flatMap' cuando estás frente a Opcionales anidados . Aquí está el ejemplo.

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);       // ②
    }

}

Al igual que Stream, el mapa opcional # devolverá un valor envuelto por un opcional. Es por eso que obtenemos un Opcional anidado - Optional<Optional<Insurance>. Y en ②, queremos mapearlo como una instancia de Seguros, así es como sucedió la tragedia. La raíz está anidada. Opcionales. Si podemos obtener el valor central independientemente de los shells, lo haremos. Eso es lo que hace flatMap.

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

Al final, te recomiendo encarecidamente el Java 8 en acción si deseas estudiar Java8 sistemáticamente.

momonannan
fuente