Jak wykonać logikę na Opcjonalnym, jeśli nie jest obecny?


83

Chcę zamienić następujący kod za pomocą java8 Optional:

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

    return obj;
}

Poniższy pseudokod nie działa, ponieważ nie ma orElseRunmetody, ale tak czy inaczej ilustruje mój cel:

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

Co chcesz zwrócić z metody, jeśli nie ma obiektu?
Duncan Jones

Chciałbym Optionalzawsze zwracać wartość wskazaną przez parametr return metody.
członkowie około

Odpowiedzi:


124

W przypadku wersji Java 9 lub nowszej ifPresentOrElsenajprawdopodobniej chcesz:

Optional<> opt = dao.find();

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

Curry przy użyciu vavr lub podobnego kodu może dać jeszcze ładniejszy kod, ale jeszcze nie próbowałem.


63
wygląda na to, że powinno być zawarte w wersji 1 (Java 8) ... no cóż ...
ycomp

2
Tak ... Myślę też, że faktycznie przegapili to w Javie 8. I więcej ... jeśli chcesz coś zrobić, gdy wartość jest obecna, podali "ifPresent ()". Jeśli chcesz coś zrobić, gdy wartość jest obecna, a inną, gdy jej nie ma, dali "ifPresentOrElse (f1, f2)". Ale nadal brakuje, gdybym tylko chciał zrobić coś z nieobecnym (odpowiednie byłoby coś takiego jak "ifNotPresent ()"). Z ifPresentOrElse jestem zmuszony użyć funkcji present, która nic nie robi w późniejszym przypadku.
hbobenicio

Jeśli możesz wprowadzić framework, spójrz na Vavr (dawniej Javaslang) i ich Option, ma on metodę onEmpty
Andreas

Zamiast tego użyj Java 9 lub if, inaczej. vavr nie jest zbyt fajny
senseiwu,

jest to zbyt skomplikowane tylko dla strita, jeśli w przeciwnym razie!
JBarros35

37

Myślę, że nie da się tego zrobić w jednym oświadczeniu. Lepiej zrób:

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

37
To może być właściwa odpowiedź, ale w jakim sensie jest ona lepsza od nullczeków? Z mojego punktu widzenia jest gorzej bez pliku orElse....
DaRich

4
@DaRich, możesz zapomnieć o null w środku swojego kodu, co skutkuje NPE. Ale nie możesz zignorować Optionalprzypadkowej decyzji, zawsze jest to wyraźna (i niebezpieczna) decyzja.
Dherik

17

Dla Java 8 Spring oferuje ifPresentOrElseod "Metody narzędziowe do pracy z opcjami", aby osiągnąć to, co chcesz. Przykład:

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

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

11

Będziesz musiał podzielić to na wiele instrukcji. Oto jeden sposób, aby to zrobić:

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

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

Innym sposobem (prawdopodobnie nadmiernie zaprojektowanym) jest użycie map:

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

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

Jeśli obj.setAvailablewygodnie wrócisz obj, możesz po prostu drugi przykład:

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

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

9

Przede wszystkim dao.find()powinieneś albo zwrócić plik, Optional<Obj>albo będziesz musiał go utworzyć.

na przykład

Optional<Obj> = dao.find();

lub możesz to zrobić samodzielnie:

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

ten wróci, Optional<Obj>jeśli jest obecny lub Optional.empty()nieobecny.

A teraz przejdźmy do rozwiązania,

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

To jedyna wkładka, której szukasz :)


9
Zwracanie wartości null niweczy cel opcji Optionals. W pytaniu PO to, co należy zrobić, jeśli obiekt nie zostanie znaleziony, jest niejednoznaczne. IMHO lepiej jest zwrócić nowo utworzony obiekt Object i być może ustawićAvailable na false. Oczywiście, tutaj OP zarejestrował się fatalnie, co oznacza, że ​​prawdopodobnie zamierza zakończyć, więc to nie ma znaczenia.
Somaiah Kumbera

To rozwiązanie zwraca an Object, podczas gdy oryginalne pytanie dotyczy metody zwracającej Optional<Object>. Moja (starsza) odpowiedź jest bardzo podobna, ale różni się w ten sposób: stackoverflow.com/a/36681079/3854962
UTF_or_Death

Dlaczego używać flatMap?
Lino,

ponieważ zwraca opcjonalną zamiast wartości. FlatMap konwertuje opcjonalne <opcjonalne <X>> na opcjonalne <X>
Aman Garg

9

Nie jest.orElseRun metoda, ale to się nazywa .orElseGet.

Główny problem z pseudokodem polega na tym, .isPresentże nie zwraca pliku Optional<>. Ale .mapzwraca, Optional<>który ma orElseRunmetodę.

Jeśli naprawdę chcesz to zrobić w jednej instrukcji, jest to możliwe:

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

Ale to jest jeszcze bardziej chrupiące niż to, co miałeś wcześniej.


3

Udało mi się wymyślić kilka rozwiązań „jednej linii”, na przykład:

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

lub

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

lub

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

Nie wygląda to zbyt ładnie, coś takiego orElseRunbyłoby znacznie lepsze, ale myślę, że ta opcja z Runnable jest akceptowalna, jeśli naprawdę chcesz rozwiązanie jednokreskowe.


1

W Javie 8 Optionalmożna to zrobić za pomocą:

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

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

1

Dla tych z Was, którzy chcą wywołać efekt uboczny tylko wtedy, gdy nie ma opcji

tj. odpowiednik ifAbsent()lub ifNotPresent()tutaj jest niewielką modyfikacją świetnych odpowiedzi już udzielonych.

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

1
ifPresentOrElse wymaga Java 9.
JL_SO


0

ifPresentOrElse może również obsługiwać przypadki nullpointers. Łatwe podejście.

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

-2

Przypuszczam, że nie możesz zmienić dao.find()metody zwracania instancji Optional<Obj>, więc musisz samodzielnie utworzyć odpowiednią.

Poniższy kod powinien ci pomóc. Utworzyłem klasę OptionalAction, która zapewnia mechanizm 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
Jeśli głosujesz przeciw, zostaw komentarz. Pomaga mi to poprawić odpowiedź.
mike

Zgadzam się, że przeciwnik powinien komentować tutaj. Zakładam, że dzieje się tak, ponieważ szukałem refaktoryzacji kodu java7 do kodu java8, podczas gdy stary kod składał się z 8 linii. I jeśli byłoby zastąpić go z sugestią, że nie pomoże nikomu, ale tylko pogorszyć sytuację.
członkowie wokół

Nie rozumiem twojego punktu. Zrobiłem refaktoryzację java 7 do 8, prawda? A w jakim stopniu pogorszyłoby to sytuację? Nie widzę żadnych błędów w tym rozwiązaniu. Można się spierać, czy cały sens posiadania innego (lub obejścia) Optionalma sens. Ale poprawnie odpowiedziałem na twoje pytanie i podałem działający przykład.
Mike

Twoje rozwiązanie wydaje mi się całkowicie słuszne, Mike. W każdym razie wprowadzenie jawnych klas, takich OptionalActionjak obejście możliwości przeniesienia kodu do java8, wydaje się nieco przesadne, jeśli w java7 jest to już tylko kilka linijek.
członkowie wokół

2
Opcjonalny obiekt <DbObject> = (v == null)? Optional.empty (): Optional.of (v); można przepisać na: Opcjonalne <DbObject> obiekt = Optional.ofNullable (v);
Geir
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.