Estilo funcional do Optional.ifPresent e se não estiver presente do Java 8?


274

No Java 8, quero fazer algo com um Optionalobjeto, se estiver presente, e fazer outra coisa, se não estiver presente.

if (opt.isPresent()) {
  System.out.println("found");
} else {
  System.out.println("Not found");
}

Este não é um 'estilo funcional', no entanto.

Optionaltem um ifPresent()método, mas não consigo encadear um orElse()método.

Assim, não posso escrever:

opt.ifPresent( x -> System.out.println("found " + x))
   .orElse( System.out.println("NOT FOUND"));

Em resposta a @assylias, acho que não Optional.map()funciona para o seguinte caso:

opt.map( o -> {
  System.out.println("while opt is present...");
  o.setProperty(xxx);
  dao.update(o);
  return null;
}).orElseGet( () -> {
  System.out.println("create new obj");
  dao.save(new obj);
  return null;
});

Nesse caso, quando optpresente, atualizo sua propriedade e salvo no banco de dados. Quando não está disponível, eu crio um novo obje salvo no banco de dados.

Nota nas duas lambdas eu tenho que voltar null.

Mas quando optestiver presente, as duas lambdas serão executadas. objserá atualizado e um novo objeto será salvo no banco de dados. Isso ocorre por causa do return nullprimeiro lambda. E orElseGet()continuará a executar.


53
Use sua primeira amostra. É lindo .
Sotirios Delimanolis

3
Sugiro que você pare de forçar determinado comportamento ao usar uma API que não foi projetada para esse comportamento. O seu primeiro exemplo me parece bom, exceto por algumas pequenas observações de estilo, mas essas são opinativas.
Skiwi

4
@ smallufo: substitua return null;por return o;(ambos). No entanto, tenho a forte sensação de que você está trabalhando no lugar errado. Você deve trabalhar no site que produziu isso Optional. Nesse local, deve haver uma maneira de realizar a operação desejada sem o intermediário Optional.
Holger

10
Java 9 implementa uma solução para o seu problema: iteratrlearning.com/java9/2016/09/05/java9-optional.html
pisaruk

2
Eu acho que a razão pela qual isso não pode ser feito facilmente é de propósito. Opcional não deve controlar o fluxo, mas valorizar a transformação. Eu sei que ifPresentisso contradiz. Todos os outros métodos se referem ao valor e não às ações.
AlikElzin-kilaka 20/07

Respostas:


109

Para mim, a resposta de @Dane White é boa, primeiro não gostei de usar o Runnable, mas não encontrei alternativas, aqui outra implementação preferi mais

public class OptionalConsumer<T> {
    private Optional<T> optional;

    private OptionalConsumer(Optional<T> optional) {
        this.optional = optional;
    }

    public static <T> OptionalConsumer<T> of(Optional<T> optional) {
        return new OptionalConsumer<>(optional);
    }

    public OptionalConsumer<T> ifPresent(Consumer<T> c) {
        optional.ifPresent(c);
        return this;
    }

    public OptionalConsumer<T> ifNotPresent(Runnable r) {
        if (!optional.isPresent()) {
            r.run();
        }
        return this;
    }
}

Então :

Optional<Any> o = Optional.of(...);
OptionalConsumer.of(o).ifPresent(s ->System.out.println("isPresent "+s))
            .ifNotPresent(() -> System.out.println("! isPresent"));

Atualização 1:

a solução acima para a maneira tradicional de desenvolvimento quando você tem o valor e deseja processá-lo, mas e se eu quiser definir a funcionalidade e a execução será então, verifique abaixo o aprimoramento;

public class OptionalConsumer<T> implements Consumer<Optional<T>> {
private final Consumer<T> c;
private final Runnable r;

public OptionalConsumer(Consumer<T> c, Runnable r) {
    super();
    this.c = c;
    this.r = r;
}

public static <T> OptionalConsumer<T> of(Consumer<T> c, Runnable r) {
    return new OptionalConsumer(c, r);
}

@Override
public void accept(Optional<T> t) {
    if (t.isPresent()) {
        c.accept(t.get());
    }
    else {
        r.run();
    }
}

Então poderia ser usado como:

    Consumer<Optional<Integer>> c=OptionalConsumer.of(System.out::println, ()->{System.out.println("Not fit");});
    IntStream.range(0, 100).boxed().map(i->Optional.of(i).filter(j->j%2==0)).forEach(c);

Neste novo código, você tem três coisas:

  1. pode definir a funcionalidade antes da existência do objeto easy.
  2. não criando referência de objeto para cada opcional, apenas um, você tem menos memória e menos GC.
  3. está implementando consumidor para melhor uso com outros componentes.

a propósito, agora seu nome é mais descritivo, na verdade é Consumidor>


3
Deve usar Optional.ofNullable (o) em vez de Optional.of (o)
traeper

2
Você precisa usar ofNullable se não tiver certeza se o valor que vai usar tem nulo ou não e não precisa enfrentar o NPE, e caso tenha certeza de que não é nulo ou se não se importa se obtém NPE.
Bassem Reda Zohdy

1
Eu acho que a classe OptionalConsumer parece melhor do que se / outro no código. Obrigado! :)
witek1902

207

Se você estiver usando Java 9+, poderá usar o ifPresentOrElse()método:

opt.ifPresentOrElse(
   value -> System.out.println("Found: " + value),
   () -> System.out.println("Not found")
);

3
Bom porque é quase tão limpo como padrão de correspondência em Scala
sscarduzio

Dois lambda assim são muito feios. Eu acho que se / mais é muito mais limpo para esses casos.
john16384 18/03

1
@ john16384 OK, se você achar feio, vou excluir minha resposta (não).
ZhekaKozlov

Isso é muito bom, mas a pergunta foi feita especificamente para o JDK8 porque ifPresentOrElse não está disponível.
hreinn

81

O Java 9 apresenta

ifPresentOrElse se um valor estiver presente, executa a ação especificada com o valor, caso contrário, executa a ação baseada em vazio especificada.

Veja excelente Opcional na cábula Java 8 .

Ele fornece todas as respostas para a maioria dos casos de uso.

Breve resumo abaixo

ifPresent () - faça algo quando Opcional estiver definido

opt.ifPresent(x -> print(x)); 
opt.ifPresent(this::print);

filter () - rejeita (filtra) determinados valores opcionais.

opt.filter(x -> x.contains("ab")).ifPresent(this::print);

map () - transforma valor se presente

opt.map(String::trim).filter(t -> t.length() > 1).ifPresent(this::print);

orElse () / orElseGet () - ficando vazio Opcional para o padrão T

int len = opt.map(String::length).orElse(-1);
int len = opt.
    map(String::length).
    orElseGet(() -> slowDefault());     //orElseGet(this::slowDefault)

orElseThrow () - lança preguiçosamente exceções em vazio

opt.
filter(s -> !s.isEmpty()).
map(s -> s.charAt(0)).
orElseThrow(IllegalArgumentException::new);

66
Na verdade, isso não responde à pergunta do OP. Ele responde a muitos usos comuns, mas não ao que o OP pediu.
Captain Man

1
@CaptainMan realmente faz; a expressão opt.map ("found"). orElse ("not found") preenche a conta.
Matt

4
@ Matt não, o OP está solicitando especificamente ações a serem executadas quando o opcional está / não está presente, para não retornar um valor quando está ou não está. O OP até menciona algo semelhante na pergunta usando orElseGet explicando por que não vai funcionar.
Capitão Man

2
@CaptainMan Entendo o seu ponto. Eu acho que ele poderia fazê-lo funcionar se não retornasse nulo do map, mas é um pouco estranho pedir uma solução funcional para que você possa chamar um DAO. Parece-me que faria mais sentido retornar o objeto atualizado / novo desse map.orElsebloco e fazer o que você precisa fazer com o objeto retornado.
Matt

1
Acho que o mapfoco no próprio fluxo e não se destina a "fazer coisas com outro objeto, dependendo do status desse elemento no fluxo". É bom saber que ifPresentOrElseé adicionado em Java 9.
WesternGun

53

Uma alternativa é:

System.out.println(opt.map(o -> "Found")
                      .orElse("Not found"));

Eu não acho que melhora a legibilidade, no entanto.

Ou, como Marko sugeriu, use um operador ternário:

System.out.println(opt.isPresent() ? "Found" : "Not found");

2
Obrigado @assylias, mas não acho que Optional.map () funcione para o caso (consulte minha atualização de contexto).
Smallufo 21/05

2
@smallufo Você precisaria voltar new Object();no seu primeiro lambda, mas para ser honesto, isso se torna muito feio. Gostaria de manter um if / else para o seu exemplo atualizado.
assylias 21/05

Concordo: usar mapapenas retornar Optionalpara encadear torna o código mais difícil de entender, enquanto mapse supõe que ele seja literalmente mapeado para alguma coisa.
Tiina

40

Outra solução seria usar funções de ordem superior da seguinte maneira

opt.<Runnable>map(value -> () -> System.out.println("Found " + value))
   .orElse(() -> System.out.println("Not Found"))
   .run();

8
Em meus olhos a melhor solução até agora sem JDK 9.
Semaphor

5
Uma explicação seria ótima. Estou me perguntando, por que você precisa usar o mapa executável (?) E o que a value -> () -> sysoparte significa.
froehli

Obrigado por esta solução! Eu acho que a razão de usar Runnable é que o mapa não retorna nenhum valor e, com o Runnable, ele retorna lambda e, como resultado do mapa, lambda, nós o executamos depois. Portanto, se você tiver retornado valor, poderá usar o seguinte:String result = opt.map(value -> "withOptional").orElse("without optional");
nanotexnik

21

Não há uma ótima maneira de fazer isso imediatamente. Se você quiser usar a sintaxe do limpador regularmente, poderá criar uma classe de utilitário para ajudar:

public class OptionalEx {
    private boolean isPresent;

    private OptionalEx(boolean isPresent) {
        this.isPresent = isPresent;
    }

    public void orElse(Runnable runner) {
        if (!isPresent) {
            runner.run();
        }
    }

    public static <T> OptionalEx ifPresent(Optional<T> opt, Consumer<? super T> consumer) {
        if (opt.isPresent()) {
            consumer.accept(opt.get());
            return new OptionalEx(true);
        }
        return new OptionalEx(false);
    }
}

Em seguida, você pode usar uma importação estática em outro lugar para obter a sintaxe mais próxima do que deseja:

import static com.example.OptionalEx.ifPresent;

ifPresent(opt, x -> System.out.println("found " + x))
    .orElse(() -> System.out.println("NOT FOUND"));

Obrigado. Esta solução é linda. Sei que talvez não exista uma solução interna (a menos que o JDK incorpore esse método). Você OptionalEx é muito útil. De qualquer forma, obrigado.
Smallufo 22/05

Sim, gosto do resultado e do estilo que ele suporta. Então, por que não na API padrão?
precisa

Boa resposta. Nós fazemos o mesmo. Concordo que deve estar na API (ou idioma!), Mas foi recusado: bugs.openjdk.java.net/browse/JDK-8057557 .
Garrett Smith

Agradável. Isso deve fazer parte do JDK 8.1 para consideração.
Peter_pilgrim

35
Optional.ifPresentOrElse()foi adicionado ao JDK 9.
Stuart Marks

9

Se você pode usar apenas o Java 8 ou inferior:

1) se você não tem spring-dataa melhor maneira até agora é:

opt.<Runnable>map(param -> () -> System.out.println(param))
      .orElse(() -> System.out.println("no-param-specified"))
      .run();

Agora eu sei que não é tão legível e até difícil de entender para alguém, mas parece bom para mim pessoalmente e não vejo outra maneira fluente e agradável para este caso.

2) se você tiver sorte e puder usar spring-datada melhor maneira, é Opcional # ifPresentOrElse :

Optionals.ifPresentOrElse(opt, System.out::println,
      () -> System.out.println("no-param-specified"));

Se você pode usar o Java 9, definitivamente deve usar:

opt.ifPresentOrElse(System.out::println,
      () -> System.out.println("no-param-specified"));

2

O comportamento descrito pode ser alcançado usando o Vavr (anteriormente conhecido como Javaslang), uma biblioteca funcional de objetos para Java 8+, que implementa a maioria das construções do Scala (sendo o Scala uma linguagem mais expressiva com um sistema do tipo mais rico construído na JVM). É uma biblioteca muito boa para adicionar aos seus projetos Java para escrever código funcional puro.

O Vavr fornece a Optionmônada que fornece funções para trabalhar com o tipo de opção, como:

  • fold: para mapear o valor da opção nos dois casos (definido / vazio)
  • onEmpty: permite executar uma Runnableopção quando estiver vazia
  • peek: permite consumir o valor da opção (quando definido).
  • e também é, Serializablepelo contrário, o Optionalque significa que você pode usá-lo com segurança como argumento do método e membro da instância.

A opção segue as leis da mônada diferentes da "pseudo-mônada" opcional do Java e fornece uma API mais rica. E é claro que você pode fazer isso a partir de um Java opcional (e vice-versa):Option.ofOptional(javaOptional) vice versa –Vavr está focado na interoperabilidade.

Indo para o exemplo:

// AWESOME Vavr functional collections (immutable for the gread good :)
// fully convertible to Java's counterparts.
final Map<String, String> map = Map("key1", "value1", "key2", "value2");

final Option<String> opt = map.get("nonExistentKey"); // you're safe of null refs!

final String result = opt.fold(
        () -> "Not found!!!",                // Option is None
        val -> "Found the value: " + val     // Option is Some(val)
);

Leitura adicional

Referência nula, o erro de um bilhão de dólares

Nota: este é apenas um pequeno exemplo do que o Vavr oferece (correspondência de padrões, fluxos também conhecidos como listas de avaliação lenta, tipos monádicos, coleções imutáveis, ...).


1

Outra solução pode ser a seguinte:

É assim que você o usa:

    final Opt<String> opt = Opt.of("I'm a cool text");
    opt.ifPresent()
        .apply(s -> System.out.printf("Text is: %s\n", s))
        .elseApply(() -> System.out.println("no text available"));

Ou no caso de você, no caso de uso oposto, ser verdadeiro:

    final Opt<String> opt = Opt.of("This is the text");
    opt.ifNotPresent()
        .apply(() -> System.out.println("Not present"))
        .elseApply(t -> /*do something here*/);

Estes são os ingredientes:

  1. Interface de função pouco modificada, apenas para o método "elseApply"
  2. Aprimoramento opcional
  3. Um pouco de enrolar :-)

A interface de função aprimorada "cosmeticamente".

@FunctionalInterface
public interface Fkt<T, R> extends Function<T, R> {

    default R elseApply(final T t) {
        return this.apply(t);
    }

}

E a classe Wrapper opcional para aprimoramento:

public class Opt<T> {

    private final Optional<T> optional;

    private Opt(final Optional<T> theOptional) {
        this.optional = theOptional;
    }

    public static <T> Opt<T> of(final T value) {
        return new Opt<>(Optional.of(value));
    }

    public static <T> Opt<T> of(final Optional<T> optional) {
        return new Opt<>(optional);
    }

    public static <T> Opt<T> ofNullable(final T value) {
        return new Opt<>(Optional.ofNullable(value));
    }

    public static <T> Opt<T> empty() {
        return new Opt<>(Optional.empty());
    }

    private final BiFunction<Consumer<T>, Runnable, Void> ifPresent = (present, notPresent) -> {
        if (this.optional.isPresent()) {
            present.accept(this.optional.get());
        } else {
            notPresent.run();
        }
        return null;
    };

   private final BiFunction<Runnable, Consumer<T>, Void> ifNotPresent = (notPresent, present) -> {
        if (!this.optional.isPresent()) {
            notPresent.run();
        } else {
            present.accept(this.optional.get());
        }
        return null;
    };

    public Fkt<Consumer<T>, Fkt<Runnable, Void>> ifPresent() {
        return Opt.curry(this.ifPresent);
    }

    public Fkt<Runnable, Fkt<Consumer<T>, Void>> ifNotPresent() {
        return Opt.curry(this.ifNotPresent);
    }

    private static <X, Y, Z> Fkt<X, Fkt<Y, Z>> curry(final BiFunction<X, Y, Z> function) {
        return (final X x) -> (final Y y) -> function.apply(x, y);
    }
}

Isso deve funcionar e pode servir como um modelo básico de como lidar com esses requisitos.

A idéia básica aqui é a seguir. Em um mundo de programação de estilo não funcional, você provavelmente implementaria um método com dois parâmetros, onde o primeiro é um tipo de código executável que deve ser executado caso o valor esteja disponível e o outro parâmetro é o código executável que deve ser executado caso o O valor não está disponível. Para melhor legibilidade, você pode usar o curring para dividir a função de dois parâmetros em duas funções de um parâmetro cada. Foi o que basicamente fiz aqui.

Dica: Opt também fornece o outro caso de uso em que você deseja executar um trecho de código, caso o valor não esteja disponível. Isso também pode ser feito via Optional.filter.stuff, mas achei muito mais legível.

Espero que ajude!

Boa programação :-)


Você pode dizer o que não está funcionando? Eu testei novamente e para mim está funcionando?
Alessandro Giusa 11/11

Com licença, mas onde o ckass Fkt está definido? Por último, mas não menos importante, tenho um problema de compilação: Erro: (35, 17) java: a variável opcional pode não ter sido inicializada
Enrico Giurin

Fkt é definido acima como interface. Basta ler o artigo completo :-)
Alessandro Giusa

Sim. Mudei a interface EFunction para Fkt como nome. Houve um erro de digitação. Obrigado pela revisão :-) desculpe por isso.
Alessandro Giusa 19/11

Eu não acho que seja uma boa idéia escrever código como este ... Você deve usar o utilitário jdk quando puder.
Tyulpan Tyulpan

0

Caso você queira armazenar o valor:

Pair.of<List<>, List<>> output = opt.map(details -> Pair.of(details.a, details.b))).orElseGet(() -> Pair.of(Collections.emptyList(), Collections.emptyList()));

0

Supondo que você tenha uma lista e evitando o isPresent()problema (relacionado aos opcionais), você pode usar .iterator().hasNext()para verificar se não está presente.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.