Як виконати логіку на необов’язковому, якщо його немає?


83

Я хочу замінити наступний код за допомогою java8 Optional:

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

    return obj;
}

Наступний псевдокод не працює, оскільки немає orElseRunметоду, але в будь-якому випадку він ілюструє мою мету:

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

Що ви хочете повернути від методу, якщо об’єкта немає?
Дункан Джонс,

Я хотів би повертати Optionalзавжди, як зазначено параметром return методу.
membersound

Відповіді:


124

З Java 9 або вище, ifPresentOrElseшвидше за все, ви хочете:

Optional<> opt = dao.find();

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

Каррінг за допомогою vavr або подібного може отримати ще акуратніший код, але я ще не пробував.


63
здається чимось, що мало бути включеним у v1 (Java 8) ... ну ну ...
ycomp

2
Так ... Я також думаю, що вони насправді пропустили це в Java 8. І більше ... якщо ви хочете щось зробити, коли значення присутнє, вони дали "ifPresent ()". Якщо ви хочете щось зробити, коли значення присутнє, а інше, коли його немає, вони дають "ifPresentOrElse (f1, f2)". Але його все ще бракує, якщо я хочу зробити щось лише з тим, що його немає (щось на зразок "ifNotPresent ()" підійде). З ifPresentOrElse я змушений використовувати поточну функцію, яка в подальшому випадку нічого не робить.
hbobenicio

Якщо ви можете представити фреймворк, подивіться на Vavr (колишній Javaslang) та їх варіант, він має метод onEmpty
Андреас

Скоріше використовуйте Java 9 або використовуйте if, else. vavr не дуже приємний
senseiwu

це надмірно складно лише для прямого, якщо тоді!
JBarros35

37

Я не думаю, що ви можете зробити це за допомогою однієї заяви. Краще робити:

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

37
Це може бути правильною відповіддю, але в чому це перевершує nullперевірки? З моєї точки зору, гірше без orElse....
DaRich

4
@DaRich, ти можеш забути про нуль в середині коду, що призводить до NPE. Але ви не можете ігнорувати Optionalвипадково, це завжди чітке (і небезпечне) рішення.
Дерік,

17

Для Java 8 Spring пропонує ifPresentOrElse"Утилітні методи роботи з необов'язковими" для досягнення бажаного. Прикладом може бути:

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

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

11

Вам доведеться розділити це на кілька тверджень. Ось один із способів зробити це:

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

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

Інший спосіб (можливо, надмірно розроблений) - використовувати map:

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

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

Якщо obj.setAvailableзручно повертається obj, ви можете просто другий приклад:

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

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

9

Перш за все, ви dao.find()повинні або повернутиOptional<Obj> або вам доведеться створити його.

напр

Optional<Obj> = dao.find();

або ви можете зробити це самостійно, як:

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

цей повернеться, Optional<Obj>якщо присутній абоOptional.empty() якщо його немає.

Отже, давайте перейдемо до рішення,

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

Це той лайнер, який ви шукаєте :)


9
Повернення нуля перешкоджає меті Optionals. У питанні ОП, що потрібно робити, якщо об’єкт не знайдено, неоднозначно. IMHO краще повернути нещодавно створений об'єкт і, можливо, setAvailable значення false. Звичайно, тут OP зафіксував смертельний результат, а це означає, що він, ймовірно, має намір припинити свою діяльність, тому це насправді не має значення.
Somaiah Kumbera

Це рішення повертає an Object, тоді як вихідне питання стосується методу, що повертається Optional<Object>. Мій (старше) відповідь дуже схожий , але відрізняється наступним чином: stackoverflow.com/a/36681079/3854962
UTF_or_Death

Чому використання flatMap?
Lino

тому що він повертає необов’язковий замість значення. FlatMap перетворює Необов'язковий <Необов'язковий <X>> в Необов'язковий <X>
Аман Гарг,

9

Там є.orElseRun метод, але він називається.orElseGet .

Основна проблема з вашим псевдокодом полягає в тому, .isPresentщо не повертає файл Optional<>. Але .mapповертає значення, Optional<>яке маєorElseRun метод.

Якщо ви дійсно хочете зробити це за допомогою одного твердження, це можливо:

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

Але це навіть незграбніше, ніж те, що було раніше.


3

Я зміг запропонувати кілька рішень "в один рядок", наприклад:

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

або

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

або

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

Це виглядає не дуже приємно, щось на зразок orElseRunбуло б набагато краще, але я думаю, що варіант з Runnable прийнятний, якщо ви дійсно хочете рішення з одним рядком.


1

З Java 8 Optionalце можна зробити за допомогою:

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

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

1

Для тих з вас, хто хоче виконати побічний ефект лише за відсутності додаткового

тобто еквівалент ifAbsent()або ifNotPresent()тут є невеликою модифікацією чудових відповідей, які вже надані.

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

1
ifPresentOrElse вимагає Java 9.
JL_SO


0

ifPresentOrElse також може обробляти випадки нульових покажчиків. Легкий підхід.

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

-2

Припускаю, ви не можете змінити dao.find()метод повернення екземпляраOptional<Obj> , тому вам доведеться створити відповідний сам.

Наступний код повинен вам допомогти. Я створив клас OptionalAction, який надає вам механізм 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
Будь ласка, залиште коментар, якщо ви голосуєте проти. Це допомагає мені покращити відповідь.
mike

Я погоджуюсь, що голос проти цього має коментувати тут. Я припускаю, що це тому, що я прагнув перетворити java7 на java8 код, тоді як старий код складався з 8 рядків. І якщо я б замінити його з вашою пропозицією , яке не допомогло б нікому, а просто зробити ще гірше.
учасники, які звучать

Я не розумію вашої думки. Я зробив рефакторинг Java 7 до 8, чи не так? І наскільки це погіршило б ситуацію? Я не бачу недоліків у цьому рішенні. Можна сперечатися, чи має сенс вся суть у тому, щоб мати інше (або обхідний шлях) Optional. Але я правильно відповів на ваше запитання і навів робочий приклад.
mike

Ваше рішення здається мені цілком дійсним, Майк. У будь-якому випадку введення явних класів, наприклад, OptionalActionяк обхідний шлях для можливості перенесення коду на java8, здається трохи надмірно розробленим, якщо в java7 це вже лише кілька вкладок.
membersound

2
Необов’язковий об’єкт <DbObject> = (v == null)? Optional.empty (): Необов’язково.of (v); може бути переписаний на: Необов’язковий об’єкт <DbObject> = Необов’язково.ofNullable (v);
Гір
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.