Java 8 - Differenza tra Optional.flatMap e Optional.map


162

Qual è la differenza tra questi due metodi: Optional.flatMap()e Optional.map()?

Un esempio sarebbe apprezzato.



5
@AlexisC. Il tuo link riguarda la mappa di Stream e flatMap, non facoltativo.
Eran,

1
@Eran Non importa, se capisci come funziona map / flatMap che sia per uno Stream o meno, è lo stesso per un Opzionale. Se l'op ha capito come funziona per uno Stream, non dovrebbe porre questa domanda. Il concetto è lo stesso.
Alexis C.,

2
@AlexisC. Non proprio. La flatMap di Optional ha poco in comune con la flatMap di Stream.
Eran,

1
@Eran Sto parlando della differenza concettuale tra una mappa e una flatMap, non sto facendo una corrispondenza uno a uno tra Stream#flatMape Optional#flatMap.
Alexis C.,

Risposte:


166

Utilizzare mapse la funzione restituisce l'oggetto necessario o flatMapse la funzione restituisce un Optional. Per esempio:

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

Entrambe le istruzioni di stampa stampano la stessa cosa.


5
Domanda: [flat]Mapchiamereste mai la funzione di mappatura con un input == null? La mia comprensione è che gli Optionalsmistamenti se assenti - il [JavaDoc] ( docs.oracle.com/javase/8/docs/api/java/util/… ) sembra supportarlo - " Se è presente un valore, applica .. . ".
Boris the Spider,

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

14
@DiegoMartinoia Optional.of(null)è un Exception. Optional.ofNullable(null) == Optional.empty().
Boris the Spider,

1
@BoristheSpider sì, hai ragione. Stavo cercando di rispondere alla tua domanda, ma penso di averlo reso ancora più poco chiaro: concettualmente, Optional.ofNullable (null) NON dovrebbe essere vuoto, ma in pratica è considerato così, e quindi map / flatmap non vengono eseguiti.
Diego Martinoia,

1
Penso che l'input non dovrebbe mai essere nullo in getOutputOpt o getOutput
DanyalBurke

55

Entrambi assumono una funzione dal tipo di opzionale a qualcosa.

map()applica la funzione "così com'è " sull'opzionale che hai:

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

Cosa succede se la tua funzione è una funzione di T -> Optional<U>?
Il tuo risultato è ora unOptional<Optional<U>> !

Ecco di cosa flatMap()si tratta: se la tua funzione restituisce già una Optional, flatMap()è un po 'più intelligente e non la avvolge due volte, ritornando Optional<U>.

È la composizione di due modi di dire funzionali: mape flatten.


7

Nota: - di seguito è illustrata la funzione mappa e flatmap, altrimenti Opzionale è progettato principalmente per essere utilizzato solo come tipo di ritorno.

Come già saprai, Opzionale è un tipo di contenitore che può contenere o meno un singolo oggetto, quindi può essere utilizzato ovunque preveda un valore nullo (potresti non vedere NPE se usi Opzionalmente correttamente). Ad esempio, se hai un metodo che prevede un oggetto persona che potrebbe essere nullable, potresti voler scrivere il metodo in questo modo:

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
}

Qui hai restituito un tipo di stringa che viene automaticamente racchiuso in un tipo opzionale.

Se la classe di persona è simile a questa, il telefono è anche opzionale

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

In questo caso, invocando la funzione mappa, il valore restituito verrà spostato in Opzionale e produrrà qualcosa del tipo:

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; Non chiamare mai il metodo get (se necessario) su un Opzionale senza verificarlo con isPresent () a meno che non si possa vivere senza NullPointerExceptions.


1
Penso che questo esempio possa distrarre dalla natura della tua risposta perché la tua classe Personsta abusando Optional. È contro l'intenzione dell'API di utilizzare Optionalmembri come questo - vedi mail.openjdk.java.net/pipermail/jdk8-dev/2013-Settembre/…
8bitjunkie

@ 8bitjunkie Grazie per averlo sottolineato, differisce dall'Opzione di Scala ..
SandeepGodara,

6

Ciò che mi ha aiutato è stato uno sguardo al codice sorgente delle due funzioni.

Mappa : racchiude il risultato in un Opzionale.

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 - restituisce l'oggetto 'grezzo'

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
Cosa intendi con flatMap"restituisce l'oggetto 'grezzo'"? flatMaprestituisce anche l'oggetto mappato "avvolto" in un Optional. La differenza è che, nel caso di flatMap, la funzione mapper avvolge l'oggetto mappato Optionalmentre lo mapstesso avvolge l'oggetto Optional.
Derek Mahar,

@DerekMahar ha eliminato il mio, non è necessario ripubblicarlo, perché hai modificato il tuo commento nel modo giusto.
massimo

3
  • Optional.map():

Prende ogni elemento e se il valore esiste, viene passato alla funzione:

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

Ora aggiunto ha uno dei tre valori: trueoppure falseracchiuso in un Opzionale , se optionalValuepresente, o in un Opzionale vuoto in caso contrario.

Se non hai bisogno di elaborare il risultato che puoi semplicemente usare ifPresent(), non ha valore di ritorno:

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

Funziona in modo simile allo stesso metodo dei flussi. Appiattisce il flusso di flussi. Con la differenza che se il valore viene presentato, viene applicato alla funzione. Altrimenti, viene restituito un facoltativo vuoto.

Puoi usarlo per comporre chiamate di funzioni di valore opzionali.

Supponiamo di avere metodi:

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

Quindi puoi calcolare la radice quadrata dell'inverso, come:

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

o, se preferisci:

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

Se inverse()o squareRoot()restituisce Optional.empty(), il risultato è vuoto.


1
Questo non viene compilato. Entrambe le espressioni restituiscono un <Double> facoltativo anziché il doppio a cui si sta assegnando il risultato.
JL_SO,

@JL_SO hai ragione. Perché inverse ha Optional<Double>tipo come tipo restituito.
nazar_art,

3

Va bene. Devi solo usare 'flatMap' quando ti trovi di fronte a Optionals nidificati . Ecco l'esempio.

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

}

Come Stream, la # Mappa opzionale restituirà un valore racchiuso da un Opzionale. Ecco perché otteniamo un opzionale nidificato - Optional<Optional<Insurance>. E a ②, vogliamo mapparlo come un'istanza assicurativa, ecco come è avvenuta la tragedia. La radice è nidificata Optionals. Se riusciamo a ottenere il valore principale indipendentemente dalle shell, lo faremo. Questo è ciò che fa flatMap.

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

Alla fine, ti ho fortemente raccomandato Java 8 In Action se desideri studiare Java8 in modo sistematico.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.