Java 8 - Diferença entre Optional.flatMap e Optional.map

162

Qual é a diferença entre esses dois métodos: Optional.flatMap()e Optional.map()?

Um exemplo seria apreciado.

co-dependente
fonte
5
@AlexisC. Seu link é sobre o mapa e o flatMap do Stream, não opcional.
Eran
1
@ Eran Isso não importa, se você entende como o map / flatMap funciona, seja para um stream ou não, é o mesmo para um opcional. Se o op entendeu como funciona para um Stream, ele não deveria fazer essa pergunta. O conceito é o mesmo.
Alexis C.
2
@AlexisC. Na verdade não. O flatMap opcional tem pouco em comum com o flatMap do Stream.
Eran
1
@Eran Estou falando sobre a diferença conceitual entre um mapa e um flatMap, não estou fazendo uma correspondência individual entre Stream#flatMape Optional#flatMap.
Alexis C.

Respostas:

166

Use mapse a função retornar o objeto que você precisa ou flatMapse a função retornar um Optional. Por exemplo:

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

As duas instruções print imprimem a mesma coisa.

assylias
fonte
5
Pergunta: [flat]Mapalguma vez chamaria a função de mapeamento com um input == null? Meu entendimento é que os Optionalordenamentos se estiverem ausentes - o [JavaDoc] ( docs.oracle.com/javase/8/docs/api/java/util/… ) parece fazer o backup - " Se houver um valor presente, aplique .. . "
Boris the Spider
1
@BoristheSpider Optional.of (null)! = Optional.empty () #
458 Diego Martinoia
14
@DiegoMartinoia Optional.of(null)é um Exception. Optional.ofNullable(null) == Optional.empty().
Boris the Spider
1
@BoristheSpider sim, você está certo. Eu estava tentando responder à sua pergunta, mas acho que a tornei ainda mais clara: conceitualmente, Optional.ofNullable (null) NÃO deve estar vazio, mas na prática é considerado, e, portanto, map / flatmap não é executado.
Diego Martinoia
1
Acho entrada nunca deve ser nula em qualquer getOutputOpt ou getOutput
DanyalBurke
55

Ambos assumem uma função do tipo do opcional para algo.

map()aplica a função " como está " no opcional que você possui:

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

O que acontece se sua função for uma função T -> Optional<U>?
O seu resultado é agora um Optional<Optional<U>>!

É disso que flatMap()se trata: se sua função já retorna um Optional, flatMap()é um pouco mais inteligente e não o envolve duas vezes, retornando Optional<U>.

É a composição de dois idiomas funcionais: mape flatten.

Diego Martinoia
fonte
7

Nota: - abaixo está a ilustração da função mapa e mapa plano, caso contrário, o opcional é projetado principalmente para ser usado apenas como um tipo de retorno.

Como você já deve saber, Opcional é um tipo de contêiner que pode ou não conter um único objeto; portanto, ele pode ser usado sempre que você antecipar um valor nulo (você nunca verá o NPE se usar o Opcional corretamente). Por exemplo, se você possui um método que espera um objeto pessoal que possa ser anulável, você pode escrever o método da seguinte forma:

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
}

Aqui você retornou um tipo de String que é automaticamente agrupado em um tipo opcional.

Se a classe da pessoa tiver essa aparência, o telefone também é opcional

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

Nesse caso, invocar a função map envolverá o valor retornado em Opcional e produzirá 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));
}

PS; Nunca chame o método get (se necessário) em um Opcional sem verificar com isPresent (), a menos que você não possa viver sem NullPointerExceptions.

SandeepGodara
fonte
1
Acho que este exemplo provavelmente distrairá a natureza da sua resposta, porque sua classe Personestá sendo mal utilizada Optional. É contra a intenção da API usar Optionalmembros como este - consulte mail.openjdk.java.net/pipermail/jdk8-dev/2013-Setembro/…
8bitjunkie
@ 8bitjunkie Obrigado por apontar isso, difere da opção do Scala .. #
SandeepGodara 21/17/17
6

O que me ajudou foi uma olhada no código fonte das duas funções.

Mapa - agrupa o resultado em um 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 - retorna o objeto 'bruto'

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
fonte
1
O que você quer dizer com flatMap"retorna o objeto 'bruto'"? flatMaptambém retorna o objeto mapeado "empacotado" em um Optional. A diferença é que, no caso de flatMap, a função mapeador envolve o objeto mapeado no Optionalmomento em que mapele envolve o objeto Optional.
Derek Mahar
O @DerekMahar excluiu o meu, não há necessidade de republicá-lo, porque você editou seu comentário corretamente.
maxxyme
3
  • Optional.map():

Pega todos os elementos e, se o valor existe, é passado para a função:

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

Agora adicionado possui um dos três valores: trueou falseagrupado em um Opcional , se optionalValuepresente, ou um Opcional vazio, caso contrário.

Se você não precisar processar o resultado que pode usar ifPresent(), ele não terá valor de retorno:

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

Funciona de maneira semelhante ao mesmo método de fluxos. Nivela o fluxo de fluxos. Com a diferença de que, se o valor for apresentado, ele será aplicado à função. Caso contrário, um opcional vazio será retornado.

Você pode usá-lo para compor chamadas de funções de valor opcionais.

Suponha que tenhamos 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));
}

Então você pode calcular a raiz quadrada do inverso, como:

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

ou, se você preferir:

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

Se o inverse()ou os squareRoot()retornos Optional.empty(), o resultado está vazio.

nazar_art
fonte
1
Isso não compila. Ambas as suas expressões retornam um <Duplo> opcional em vez do Duplo ao qual você está atribuindo o resultado.
JL_SO 5/06/19
@JL_SO você está certo. Porque inverso tem Optional<Double>tipo como tipo de retorno.
nazar_art
3

OK. Você só precisa usar 'flatMap' quando estiver enfrentando opcionais aninhados . Aqui está o exemplo.

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

}

Como o Stream, o mapa # opcional retornará um valor agrupado por um opcional. É por isso que obtemos um Opcional aninhado - Optional<Optional<Insurance>. E em ②, queremos mapeá-lo como uma instância de seguro, foi assim que a tragédia aconteceu. A raiz está aninhada opcional. Se conseguirmos obter o valor principal, independentemente das conchas, faremos isso. É isso que o flatMap faz.

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

No final, eu recomendo fortemente o Java 8 In Action para você, se você gostaria de estudar o Java8 sistematicamente.

momonannan
fonte