Estilo funcional de Java 8's Optional.ifPresent y if-not-Present?

274

En Java 8, quiero hacer algo a un Optionalobjeto si está presente, y hacer otra cosa si no está presente.

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

Sin embargo, este no es un 'estilo funcional'.

Optionaltiene un ifPresent()método, pero no puedo encadenar un orElse()método.

Por lo tanto, no puedo escribir:

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

En respuesta a @assylias, no creo que Optional.map()funcione para el siguiente 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;
});

En este caso, cuando optestá presente, actualizo su propiedad y la guardo en la base de datos. Cuando no está disponible, creo uno nuevo objy lo guardo en la base de datos.

Nota en las dos lambdas tengo que volver null.

Pero cuando optestá presente, ambas lambdas se ejecutarán. objse actualizará y se guardará un nuevo objeto en la base de datos. Esto se debe return nulla la primera lambda. Y orElseGet()continuará ejecutándose.

smallufo
fuente
53
Usa tu primera muestra. Es hermosa .
Sotirios Delimanolis
3
Le sugiero que deje de forzar cierto comportamiento cuando use una API que no esté diseñada para ese comportamiento. Su primer ejemplo me parece bien, aparte de algunos pequeños comentarios de estilo, pero esos son de opinión.
skiwi
44
@smallufo: reemplazar return null;con return o;(ambos). Sin embargo, tengo la fuerte sensación de que estás trabajando en el lugar equivocado. Deberías trabajar en el sitio que produjo eso Optional. En ese lugar debe haber una forma de realizar la operación deseada sin el intermedio Optional.
Holger
10
Java 9 implementa una solución para su problema: iteratrlearning.com/java9/2016/09/05/java9-optional.html
pisaruk
2
Creo que la razón por la que esto no se puede hacer fácilmente es a propósito. Opcional no debe hacer control de flujo, sino más bien valor de transformación. Sé que ifPresentesto contradice esto. Todos los demás métodos se refieren al valor y no a las acciones.
AlikElzin-kilaka

Respuestas:

109

Para mí, la respuesta de @Dane White está bien, primero no me gustó usar Runnable pero no pude encontrar ninguna alternativa, aquí otra implementación que preferí más

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

Luego :

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

Actualización 1:

la solución anterior para la forma tradicional de desarrollo cuando tiene el valor y desea procesarlo, pero qué pasa si quiero definir la funcionalidad y la ejecución será entonces, verifique la mejora a continuación;

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

Entonces podría usarse 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);

En este nuevo código tienes 3 cosas:

  1. Puede definir la funcionalidad antes de existir del objeto fácil.
  2. no crea referencia de objeto para cada Opcional, solo uno, tiene menos memoria que menos GC.
  3. está implementando al consumidor para un mejor uso con otros componentes.

por cierto, ahora su nombre es más descriptivo, en realidad es Consumidor>

Bassem Reda Zohdy
fuente
3
Debería usar Opcional
Anulable
2
Debe usar ofNullable si no está seguro de si el valor que va a usar es nulo o no y no necesita enfrentar NPE, y en caso de que esté seguro de que no es nulo o no le importa si obtiene NPE.
Bassem Reda Zohdy
1
Creo que la clase OptionalConsumer se ve mejor que if / else en el código. ¡Gracias! :)
witek1902
207

Si está utilizando Java 9+, puede usar el ifPresentOrElse()método:

opt.ifPresentOrElse(
   value -> System.out.println("Found: " + value),
   () -> System.out.println("Not found")
);
ZhekaKozlov
fuente
3
Agradable porque es casi tan limpio como la coincidencia de patrones en Scala
sscarduzio
Dos lambda's así es bastante feo. Creo que si / más está mucho más limpio para estos casos.
john16384
1
@ john16384 OK, si te parece feo, entonces borraré mi respuesta (no).
ZhekaKozlov
Esto es muy bueno, pero la pregunta estaba destinada específicamente para JDK8 porque ifPresentOrElse no está disponible.
hreinn
81

Java 9 presenta

ifPresentOrElse si hay un valor presente, realiza la acción dada con el valor, de lo contrario realiza la acción basada en vacío dada.

Ver excelente Opcional en Java 8 hoja de trucos .

Proporciona todas las respuestas para la mayoría de los casos de uso.

Breve resumen a continuación

ifPresent (): hace algo cuando se establece Opcional

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

filter () - rechazar (filtrar) ciertos valores opcionales.

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

map () - transforma el valor si está presente

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

orElse () / orElseGet (): se vuelve vacío Opcional al valor predeterminado T

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

orElseThrow () - lanza perezosamente excepciones en vacío Opcional

opt.
filter(s -> !s.isEmpty()).
map(s -> s.charAt(0)).
orElseThrow(IllegalArgumentException::new);
Bartosz Bilicki
fuente
66
En realidad, esto no responde la pregunta de OP. Responde a muchos usos comunes, pero no a lo que OP preguntó.
Capitán Man
1
@CaptainMan en realidad lo hace; la expresión opt.map ("encontrado") o Else ("no encontrado") llena la factura.
Matt
44
@Matt no, OP solicita específicamente acciones que realizó cuando lo opcional está / no está presente, para no devolver un valor cuando está o no está. OP incluso menciona algo similar en la pregunta usando orElseGet y explica por qué no funcionará.
Capitán Man
2
@ Capitán Man veo tu punto. Creo que podría hacerlo funcionar si no regresó nulo de la map, pero es un poco extraño pedir una solución funcional para que pueda llamar a un DAO. Me parece que tendría más sentido devolver el objeto actualizado / nuevo de este map.orElsebloque y luego hacer lo que necesita hacer con el objeto devuelto.
Matt
1
Creo que se mapenfoca en la secuencia en sí y no está destinado a "hacer cosas a otro objeto dependiendo del estado de este elemento en la secuencia". Es bueno saber que ifPresentOrElsese agrega en Java 9.
WesternGun
53

Una alternativa es:

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

Sin embargo, no creo que mejore la legibilidad.

O como sugirió Marko, use un operador ternario:

System.out.println(opt.isPresent() ? "Found" : "Not found");
asilias
fuente
2
Gracias @assylias, pero no creo que Optional.map () funcione para el caso (ver mi actualización de contexto).
smallufo
2
@smallufo Tendría que regresar new Object();en su primer lambda, pero para ser honesto, eso se vuelve muy feo. Me quedaría con un if / else para su ejemplo actualizado.
Assylias
De acuerdo, usar mappara regresar solo Optionalpara encadenar hace que el código sea más difícil de entender, mientras que mapse supone que literalmente se asigna a algo.
Tiina
40

Otra solución sería utilizar funciones de orden superior de la siguiente manera

opt.<Runnable>map(value -> () -> System.out.println("Found " + value))
   .orElse(() -> System.out.println("Not Found"))
   .run();
usuario5057016
fuente
8
En mi opinión, la mejor solución hasta ahora sin JDK 9.
Semaphor
55
Una explicación sería genial. Me pregunto por qué necesita usar un mapa ejecutable (?) Y qué value -> () -> sysosignifica la parte.
froehli
¡Gracias por esta solución! Creo que la razón de usar Runnable es que out map no devuelve ningún valor y con Runnable devuelve lambda y, como resultado de map es lambda, lo ejecutamos después. Entonces, si tiene un valor de retorno, puede usar lo siguiente:String result = opt.map(value -> "withOptional").orElse("without optional");
nanotexnik el
21

No hay una excelente manera de hacerlo de forma inmediata. Si desea utilizar su sintaxis más limpia de forma regular, puede crear una clase de utilidad para ayudar:

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

Luego puede usar una importación estática en otro lugar para obtener una sintaxis cercana a la que busca:

import static com.example.OptionalEx.ifPresent;

ifPresent(opt, x -> System.out.println("found " + x))
    .orElse(() -> System.out.println("NOT FOUND"));
Dane White
fuente
Gracias. Esta solución es hermosa. Sé que tal vez no haya una solución integrada (a menos que JDK incorpore dicho método). OpcionalEx es muy útil. Gracias de todas formas.
smallufo
Sí, me gusta el resultado y el estilo que admite. Entonces, ¿por qué no en la API estándar?
guthrie
Buena respuesta. Nosotros hacemos lo mismo Estoy de acuerdo en que debería estar en la API (¡o en el idioma!), Pero se ha rechazado: bugs.openjdk.java.net/browse/JDK-8057557 .
Garrett Smith
Agradable. Esto debería ser parte del JDK 8.1 para su consideración.
peter_pilgrim
35
Optional.ifPresentOrElse()ha sido agregado a JDK 9.
Stuart Marks
9

Si solo puede usar Java 8 o inferior:

1) si no tienes spring-datala mejor manera hasta ahora es:

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

Ahora sé que no es tan legible e incluso difícil de entender para alguien, pero personalmente me parece bien y no veo otra forma fluida para este caso.

2) si tienes la suerte y puedes usar spring-datala mejor manera es Opcional # ifPresentOrElse :

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

Si puede usar Java 9, definitivamente debería usar:

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

El comportamiento descrito se puede lograr mediante el uso de Vavr (anteriormente conocido como Javaslang), una biblioteca de objetos funcionales para Java 8+, que implementa la mayoría de las construcciones de Scala (siendo Scala un lenguaje más expresivo con un sistema de tipos mucho más rico construido en JVM). Es una muy buena biblioteca para agregar a sus proyectos Java para escribir código funcional puro.

Vavr proporciona la Optionmónada que proporciona funciones para trabajar con el tipo de opción, como:

  • fold: para asignar el valor de la opción en ambos casos (definido / vacío)
  • onEmpty: permite ejecutar una Runnableopción cuando está vacía
  • peek: permite consumir el valor de la opción (cuando se define).
  • y también es Serializablelo contrario, lo Optionalque significa que puede usarlo con seguridad como argumento de método y miembro de instancia.

La opción sigue las leyes de la mónada en diferencia con la "pseudo-mónada" opcional de Java y proporciona una API más rica. Y, por supuesto, puede hacerlo desde un Opcional de Java (y Option.ofOptional(javaOptional)viceversa ): –Vavr se centra en la interoperabilidad.

Yendo al ejemplo:

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

Otras lecturas

Referencia nula, el error de mil millones de dólares

NB Este es solo un muy pequeño ejemplo de lo que ofrece Vavr (coincidencia de patrones, secuencias también conocidas como listas perezosas evaluadas, tipos monádicos, colecciones inmutables, ...).

Gerard Bosch
fuente
1

Otra solución podría ser la siguiente:

Así es como lo usas:

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

O en caso de que en el caso de uso contrario sea cierto:

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

Estos son los ingredientes:

  1. Interfaz de función poco modificada, solo para el método "elseApply"
  2. Mejora opcional
  3. Un poco de maldición :-)

La interfaz de funciones mejorada "cosméticamente".

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

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

}

Y la clase de contenedor opcional para mejora:

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

Esto debería hacer el truco y podría servir como una plantilla básica sobre cómo lidiar con tales requisitos.

La idea básica aquí es la siguiente. En un mundo de programación de estilo no funcional, probablemente implementaría un método que toma dos parámetros donde el primero es un tipo de código ejecutable que debe ejecutarse en caso de que el valor esté disponible y el otro parámetro es el código ejecutable que debe ejecutarse en caso de que el El valor no está disponible. En aras de una mejor legibilidad, puede usar curring para dividir la función de dos parámetros en dos funciones de un parámetro cada uno. Esto es lo que básicamente hice aquí.

Sugerencia: Opt también proporciona el otro caso de uso donde desea ejecutar un fragmento de código en caso de que el valor no esté disponible. Esto podría hacerse también a través de Optional.filter.stuff, pero me pareció mucho más legible.

¡Espero que ayude!

Buena programación :-)

Alessandro Giusa
fuente
¿Puedes decir lo que no funciona? Lo probé nuevamente y para mí está funcionando.
Alessandro Giusa
Disculpe, pero ¿dónde se define el ckass Fkt? Por último, pero no menos importante, tengo un problema de compilación: Error: (35, 17) Java: la variable opcional podría no haberse inicializado
Enrico Giurin
Fkt se define arriba como interfaz. Acabo de leer el artículo completo :-)
Alessandro Giusa
Si. Cambié la interfaz EFunction a Fkt como nombre. Hubo un error tipográfico. Gracias por la revisión :-) lo siento por eso.
Alessandro Giusa
No creo que sea buena idea escribir código como este ... Deberías usar la utilidad jdk cuando puedas.
Tyulpan Tyulpan
0

En caso de que desee almacenar el valor:

Pair.of<List<>, List<>> output = opt.map(details -> Pair.of(details.a, details.b))).orElseGet(() -> Pair.of(Collections.emptyList(), Collections.emptyList()));
Jután Debnath
fuente
0

Supongamos que tiene una lista y evita el isPresent()problema (relacionado con las opciones) que podría usar .iterator().hasNext()para verificar si no está presente.

Leandro Maro
fuente