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.