Как выполнить логику на необязательном, если его нет?

Я хочу заменить следующий код с помощью 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"));
}

person membersound    schedule 24.03.2015    source источник
comment
Что вы хотите вернуть из метода, если объекта нет?   -  person Duncan Jones    schedule 24.03.2015
comment
Я хотел бы всегда возвращать Optional, как указано в параметре возврата метода.   -  person membersound    schedule 24.03.2015


Ответы (13)


С Java 9 или более поздней версии ifPresentOrElse:

Optional<> opt = dao.find();

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

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

person Andreas    schedule 10.10.2016
comment
похоже на то, что должно было быть включено в v1 (Java 8)... ну ладно... - person ycomp; 25.12.2016
comment
Да... Я также думаю, что они действительно упустили это в Java 8. И еще... если вы хотите что-то сделать, когда значение присутствует, они предоставили ifPresent(). Если вы хотите что-то сделать, когда значение присутствует, и другое, когда его нет, они дали ifPresentOrElse(f1, f2). Но его все равно не хватает, если я хочу что-то сделать только с его отсутствием (подойдет что-то вроде ifNotPresent()). С ifPresentOrElse я вынужден использовать функцию настоящего времени, которая ничего не делает в последнем случае. - person hbobenicio; 21.06.2017
comment
Если вы можете представить структуру, взгляните на Vavr (бывший Javaslang) и их вариант, у него есть метод onEmpty - person Andreas; 22.06.2017
comment
Скорее используйте Java 9 или используйте if, else. вавр не очень приятный - person senseiwu; 11.12.2018
comment
это слишком сложно только для стрита if then else! - person JBarros35; 24.09.2020
comment
Конечно, должна быть опция ifNotPresent(() -> { ... }). Еще один случай, когда API-интерфейсы Java получают 70%, а затем прекращают работу, оставляя разработчика желать большего. :( Существуют законные случаи использования, когда вы не хотите ничего делать, если существует необязательный параметр, и заботитесь только о том, не существует ли его. Использование ifPresentOrElse кажется скользким для этого случай использования. - person Josh M.; 12.03.2021
comment
imo должен быть ifAbsent. Я всегда сталкиваюсь со случаями, когда это было бы полезно. - person Sollace; 15.06.2021

Я не думаю, что вы можете сделать это в одном заявлении. Лучше сделать:

if (!obj.isPresent()) {
    logger.fatal(...);   
} else {
    obj.get().setAvailable(true);
}
return obj;
person Konstantin Yovkov    schedule 24.03.2015
comment
Это может быть правильным ответом, но чем он лучше null проверок? С моей точки зрения без orElse... хуже. - person DaRich; 30.03.2017
comment
@DaRich, вы можете забыть о нуле в середине вашего кода, что приводит к NPE. Но вы не можете случайно проигнорировать Optional, это всегда явное (и опасное) решение. - person Dherik; 25.04.2018

Для Java 8 Spring предлагает ifPresentOrElse из "Служебные методы для работы с необязательными параметрами" для достижения желаемого. Примером может быть:

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

ifPresentOrElse(dao.find(), obj -> obj.setAvailable(true), () -> logger.fatal("Object not available"));
person Cmyker    schedule 18.12.2018
comment
Полезные вещи всегда прячутся глубоко, пока у фокусника хорошее настроение. - person mike; 22.07.2021

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

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));
person Duncan Jones    schedule 24.03.2015

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

e.g.

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

Это тот лайнер, который вы ищете :)

person Hasasn    schedule 27.04.2016
comment
Возврат нулевого значения противоречит цели опционов. В вопросе ОП, что нужно делать, если объект не найден, неоднозначно. ИМХО, лучше вернуть вновь созданный экземпляр объекта и, возможно, установить для него значение false. Конечно, здесь ОП зарегистрировал фатальный исход, что означает, что он, вероятно, намеревается завершить работу, так что это не имеет большого значения. - person Somaiah Kumbera; 28.04.2016
comment
Это решение возвращает Object, а исходный вопрос касается метода, возвращающего Optional<Object>. Мой (старый) ответ очень похож, но отличается следующим образом: stackoverflow.com/a/36681079/3854962 - person UTF_or_Death; 21.02.2017
comment
Зачем использовать flatMap? - person Lino; 17.07.2018
comment
потому что он возвращает необязательное значение вместо значения. FlatMap преобразует Необязательный‹Необязательный‹X›› в Необязательный‹X› - person isopropylcyanide; 29.08.2018

Существует есть метод .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();
    });
}

Но это еще более неуклюже, чем то, что у вас было раньше.

person UTF_or_Death    schedule 17.04.2016

Для тех из вас, кто хочет выполнить побочный эффект, только если необязательное значение отсутствует

то есть эквивалент ifAbsent() или ifNotPresent() здесь является небольшой модификацией уже предоставленных замечательных ответов.

myOptional.ifPresentOrElse(x -> {}, () -> {
  // logic goes here
})
person Stephen Paul    schedule 02.02.2020
comment
ifPresentOrElse требует Java 9. - person JL_SO; 12.03.2020

Мне удалось придумать пару решений «одной строки», например:

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

or

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

or

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

Выглядит не очень красиво, что-то вроде orElseRun было бы намного лучше, но я думаю, что вариант с Runnable приемлем, если вам действительно нужно однострочное решение.

person erkfel    schedule 03.12.2015

С Java 8 Optional это можно сделать с помощью:

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

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

Title: "How to execute logic on Optional if not present?"

Answer:

Используйте orElseGet() в качестве обходного пути для отсутствующего ifNotPresent(). И поскольку он ожидает, что мы что-то вернем, просто верните null.

Optional.empty().orElseGet(() -> {
    System.out.println("The object is not present");
    return null;
});

//output: The object is not present

or


Optional.ofNullable(null).orElseGet(() -> {
    System.out.println("The object is not present");
    return null;
});

//output: The object is not present

Я также использую его для простой реализации шаблона singleton с отложенной инициализацией.

public class Settings {
    private Settings(){}    
    private static Settings instance;
    public static synchronized Settings getInstance(){
        Optional.ofNullable(instance).orElseGet(() -> instance = new Settings());
        return instance;
    } 
}

Конечно, содержимое getInstance() можно записать в одну строку, напрямую возвращая первый оператор, но я хотел продемонстрировать использование orElseGet() в качестве ifNotPresent().

person Marinos An    schedule 29.06.2021


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

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

Я предполагаю, что вы не можете изменить метод 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);
      }
    }
  }
}
person mike    schedule 24.03.2015
comment
Пожалуйста, оставьте комментарий, если вы проголосовали против. Это помогает мне улучшить ответ. - person mike; 24.03.2015
comment
Я согласен, что downvoter должен прокомментировать здесь. Я предполагаю, что это потому, что я хотел преобразовать код java7 в java8, тогда как старый код состоял из 8 строк. И если я заменю его вашим предложением, это никому не поможет, а только усугубит ситуацию. - person membersound; 24.03.2015
comment
Я не понимаю вашей точки зрения. Я сделал рефакторинг java 7 до 8, не так ли? И насколько это усугубит ситуацию? Я не вижу недостатков в этом решении. Можно спорить, имеет ли смысл иметь else (или обходной путь) для Optional. Но я правильно ответил на ваш вопрос и привел рабочий пример. - person mike; 24.03.2015
comment
Твое решение кажется мне абсолютно правильным, Майк. В любом случае, введение явных классов, таких как OptionalAction, в качестве обходного пути для возможности переноса кода на java8 кажется немного переработанным, если в java7 это уже всего несколько вкладышей. - person membersound; 24.03.2015
comment
У вас должны быть все классы *Action, нет другого способа получить цепочку методов, как вы указали в своем вопросе. Конечно, это независимые от проблем утилитарные классы, которые можно использовать повторно. DbObject был введен только для примера. - person mike; 24.03.2015
comment
Необязательный объект DbObject = (v == null) ? Необязательный.пустой() : Необязательный.из(v); можно переписать следующим образом: Необязательный объект DbObject = Optional.ofNullable(v); - person Geir; 12.08.2015