¿Cómo ejecutar la lógica en Opcional si no está presente?


81

Quiero reemplazar el siguiente código usando java8 Optional:

public Obj getObjectFromDB() {
    Obj obj = dao.find();
    if (obj != null) {
        obj.setAvailable(true);
    } else {
        logger.fatal("Object not available");
    }

    return obj;
}

El siguiente pseudocódigo no funciona porque no hay un orElseRunmétodo, pero de todos modos ilustra mi propósito:

public Optional<Obj> getObjectFromDB() {
    Optional<Obj> obj = dao.find();
    return obj.ifPresent(obj.setAvailable(true)).orElseRun(logger.fatal("Object not available"));
}

¿Qué quiere devolver del método si no hay un objeto presente?
Duncan Jones

Me gustaría volver Optionalsiempre como lo indica el parámetro de retorno del método.
miembros alrededor del

Respuestas:


122

Con Java 9 o superior, ifPresentOrElselo más probable es que sea lo que desee:

Optional<> opt = dao.find();

opt.ifPresentOrElse(obj -> obj.setAvailable(true),
                    () -> logger.error("…"));

Curry usando vavr o similar puede obtener un código aún más ordenado , pero aún no lo he probado.


61
parece algo que debería haber sido incluido en v1 (Java 8) ... oh bueno ...
ycomp

2
Sí ... también creo que en realidad se perdieron eso en Java 8. Y más ... si quieres hacer algo cuando el valor está presente, dieron "ifPresent ()". Si desea hacer algo cuando el valor está presente y otra cosa cuando no lo está, le dieron "ifPresentOrElse (f1, f2)". Pero todavía falta si solo quiero hacer algo con no está presente (algo como "ifNotPresent ()" sería adecuado). Con ifPresentOrElse me veo obligado a usar una función presente que no hace nada en el último caso.
hbobenicio

Si puede introducir un marco, eche un vistazo a Vavr (antiguo Javaslang) y su opción, tiene un método onEmpty
Andreas

En su lugar, use Java 9 o use if, de lo contrario. vavr no es muy agradable
senseiwu

¡Esto es excesivamente complejo solo por un simple si no!
JBarros35

36

No creo que puedas hacerlo con una sola declaración. Mejor hazlo:

if (!obj.isPresent()) {
    logger.fatal(...);   
} else {
    obj.get().setAvailable(true);
}
return obj;

36
Esta puede ser la respuesta correcta, pero ¿de qué manera es superior a los nullcheques? Desde mi punto de vista es peor sin un orElse....
DaRich

4
@DaRich, puede olvidarse del nulo en el medio de su código, lo que resulta en el NPE. Pero no puedes ignorar una Optionalpor accidente, siempre es una decisión explícita (y peligrosa).
Dherik

15

Para Java 8 Spring ofrece ifPresentOrElsedesde "métodos de utilidad para trabajar con opcionales" para lograr lo que desea. Ejemplo sería:

import static org.springframework.data.util.Optionals.ifPresentOrElse;    

ifPresentOrElse(dao.find(), obj -> obj.setAvailable(true), () -> logger.fatal("Object not available"));

11

Tendrá que dividir esto en varias declaraciones. Aquí hay una forma de hacerlo:

if (!obj.isPresent()) {
  logger.fatal("Object not available");
}

obj.ifPresent(o -> o.setAvailable(true));
return obj;

Otra forma (posiblemente sobre-diseñada) es usar map:

if (!obj.isPresent()) {
  logger.fatal("Object not available");
}

return obj.map(o -> {o.setAvailable(true); return o;});

Si obj.setAvailableregresa convenientemente obj, entonces puede simplemente el segundo ejemplo para:

if (!obj.isPresent()) {
  logger.fatal("Object not available");
}

return obj.map(o -> o.setAvailable(true));

9

En primer lugar, dao.find()debe devolver un Optional<Obj>o tendrá que crear uno.

p.ej

Optional<Obj> = dao.find();

o puede hacerlo usted mismo como:

Optional<Obj> = Optional.ofNullable(dao.find());

éste volverá Optional<Obj>si está presente o Optional.empty()si no está presente.

Así que ahora vayamos a la solución

public Obj getObjectFromDB() {
   return Optional.ofNullable(dao.find()).flatMap(ob -> {
            ob.setAvailable(true);
            return Optional.of(ob);    
        }).orElseGet(() -> {
            logger.fatal("Object not available");
            return null;
        });
    }

Este es el delineador que estás buscando :)


9
Devolver un nulo anula el propósito de los Opcionales. En la pregunta de los OP, lo que se debe hacer si no se encuentra el objeto es ambiguo. En mi humilde opinión, es mejor devolver un objeto recién instanciado, y quizás establecer disponible en falso. Por supuesto, aquí el OP se ha registrado fatal, lo que significa que probablemente tenga la intención de terminar, por lo que realmente no importa.
Somaiah Kumbera

Esta solución devuelve un Object, mientras que la pregunta original es para un método que regresa Optional<Object>. Mi respuesta (anterior) es muy similar pero difiere de esta manera: stackoverflow.com/a/36681079/3854962
UTF_or_Death

¿Por qué el uso de flatMap?
Lino

porque devuelve un valor opcional en lugar de un valor. FlatMap convierte Opcional <Opcional <X>> en Opcional <X>
Aman Garg

9

No es un .orElseRunmétodo, pero se llama .orElseGet.

El principal problema con su pseudocódigo es que .isPresentno devuelve un Optional<>. Pero .mapdevuelve un Optional<>que tiene el orElseRunmétodo.

Si realmente desea hacer esto en una declaración, esto es posible:

public Optional<Obj> getObjectFromDB() {
    return dao.find()
        .map( obj -> { 
            obj.setAvailable(true);
            return Optional.of(obj); 
         })
        .orElseGet( () -> {
            logger.fatal("Object not available"); 
            return Optional.empty();
    });
}

Pero esto es incluso más torpe que lo que tenías antes.


3

Pude idear un par de soluciones de "una línea", por ejemplo:

    obj.map(o -> (Runnable) () -> o.setAvailable(true))
       .orElse(() -> logger.fatal("Object not available"))
       .run();

o

    obj.map(o -> (Consumer<Object>) c -> o.setAvailable(true))
       .orElse(o -> logger.fatal("Object not available"))
       .accept(null);

o

    obj.map(o -> (Supplier<Object>) () -> {
            o.setAvailable(true);
            return null;
    }).orElse(() () -> {
            logger.fatal("Object not available")
            return null;
    }).get();

No se ve muy bien, algo así orElseRunsería mucho mejor, pero creo que esa opción con Runnable es aceptable si realmente quieres una solución de una línea.


1

Para aquellos de ustedes que quieran ejecutar un efecto secundario solo si no hay un opcional

es decir, un equivalente de ifAbsent()o ifNotPresent()aquí hay una ligera modificación a las grandes respuestas ya proporcionadas.

myOptional.ifPresentOrElse(x -> {}, () -> {
  // logic goes here
})

1
ifPresentOrElse requiere Java 9.
JL_SO


0

Con Java 8 Optionalse puede hacer con:

    Optional<Obj> obj = dao.find();

    obj.map(obj.setAvailable(true)).orElseGet(() -> {
        logger.fatal("Object not available");
        return null;
    });

0

ifPresentOrElse también puede manejar casos de punteros nulos. Enfoque fácil.

   Optional.ofNullable(null)
            .ifPresentOrElse(name -> System.out.println("my name is "+ name),
                    ()->System.out.println("no name or was a null pointer"));

-2

Supongo que no puede cambiar el dao.find()método para devolver una instancia de Optional<Obj>, por lo que debe crear el apropiado usted mismo.

El siguiente código debería ayudarte. He creado la clase OptionalAction, que le proporciona el mecanismo if-else.

public class OptionalTest
{
  public static Optional<DbObject> getObjectFromDb()
  {
    // doa.find()
    DbObject v = find();

    // create appropriate Optional
    Optional<DbObject> object = Optional.ofNullable(v);

    // @formatter:off
    OptionalAction.
    ifPresent(object)
    .then(o -> o.setAvailable(true))
    .elseDo(o -> System.out.println("Fatal! Object not available!"));
    // @formatter:on
    return object;
  }

  public static void main(String[] args)
  {
    Optional<DbObject> object = getObjectFromDb();
    if (object.isPresent())
      System.out.println(object.get());
    else
      System.out.println("There is no object!");
  }

  // find may return null
  public static DbObject find()
  {
    return (Math.random() > 0.5) ? null : new DbObject();
  }

  static class DbObject
  {
    private boolean available = false;

    public boolean isAvailable()
    {
      return available;
    }

    public void setAvailable(boolean available)
    {
      this.available = available;
    }

    @Override
    public String toString()
    {
      return "DbObject [available=" + available + "]";
    }
  }

  static class OptionalAction
  {
    public static <T> IfAction<T> ifPresent(Optional<T> optional)
    {
      return new IfAction<>(optional);
    }

    private static class IfAction<T>
    {
      private final Optional<T> optional;

      public IfAction(Optional<T> optional)
      {
        this.optional = optional;
      }

      public ElseAction<T> then(Consumer<? super T> consumer)
      {
        if (optional.isPresent())
          consumer.accept(optional.get());
        return new ElseAction<>(optional);
      }
    }

    private static class ElseAction<T>
    {
      private final Optional<T> optional;

      public ElseAction(Optional<T> optional)
      {
        this.optional = optional;
      }

      public void elseDo(Consumer<? super T> consumer)
      {
        if (!optional.isPresent())
          consumer.accept(null);
      }
    }
  }
}

1
Por favor, deje un comentario si vota en contra. Esto me ayuda a mejorar la respuesta.
Mike

Estoy de acuerdo que el votante negativo debería comentar aquí. Supongo que esto se debe a que estaba buscando refactorizar java7 a código java8, mientras que el código anterior constaba de 8 líneas. Y si lo reemplazara con tu sugerencia, eso no ayudaría a nadie, solo empeoraría las cosas.
miembros alrededor del

No entiendo tu punto. Refactoricé Java 7 a 8, ¿no? ¿Y en qué medida empeoraría las cosas? No veo ningún defecto en esta solución. Se podría discutir si el objetivo de tener un else (o una solución alternativa) Optionaltiene sentido. Pero respondí correctamente a su pregunta y proporcioné un ejemplo de trabajo.
Mike

Tu solución me parece totalmente válida, Mike. De todos modos, la introducción de clases explícitas como OptionalActioncomo una solución para poder portar el código a java8 parece un poco sobreingeniería, si en java7 esto ya es solo una línea.
miembros alrededor del

2
Objeto <DbObject> opcional = (v == null)? Optional.empty (): Opcional. De (v); se puede reescribir en: Optional <DbObject> object = Optional.ofNullable (v);
Geir
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.