Нужен ли нам Optional ifNotPresent для использования в середине цепочки? - PullRequest
4 голосов
/ 27 мая 2020

Существует так много вопросов и тем по Optional API, но я не нашел ни одного для своего случая.

Например, мне нужно сначала проверить пустоту переменной Optional для целей ведения журнала , а затем проверьте значение, если оно есть, с помощью некоторого предиката. Какая бы проверка ни провалилась, мне нужно сгенерировать исключение.

Ниже приведен реальный способ обхода этой проблемы

SomeValue value = someOptional.orElseThrow(() -> {
    log.debug("nothing here");
    return new NothingSpecialHereException();
});

if (!value.isSpecial()) {
    log.debug("something here, but not special");
    throw new NothingSpecialHereException();
}

Когда я искал альтернативное решение для этого, я попробовал что-то вроде этого

SomeValue value = someOptional
    .filter(SomeValue::isSpecial)
    .orElseThrow(() -> {
        log.debug("nothing special here"); // but this case for both "no value" and "value is not special"
        return new NothingSpecialHereException();
    });

Я знаю, что в Java нет встроенного решения для этой ситуации, но похоже, что мне не хватает чего-то вроде:

SomeValue value = someOptional
   .ifNotPresent(() -> log.debug("nothing here")) // that method returns Optional for further invocatons
   .filter(val -> {
      if (!val.isSpecial()) {
          log.debug("something here, but not special");
          return false;
      }
      return true;
   })
   .orElseThrow(NothingSpecialHereException::new);

Это не первый раз, когда Мне не хватает чего-то вроде методов ifNotPresent или else* для использования в середине трубы, а не в конце. ИМО, иногда этот подход может быть более читабельным, например

optional
    .map(...)
    .filter(...)
    .ifEmpty(...) // do smth after filter, maybe even throw
    .map(...) // and continue processing

Может быть, кто-то сталкивался с такой же проблемой? Или, может быть, я пропустил лучшее решение? Может быть, есть библиотека, которая предлагает решения для этого?

Ответы [ 3 ]

1 голос
/ 27 мая 2020

JDK Optional включает (начиная с Java 9 только, что было серьезным упущением в Java 8) ifPresentOrElse, которое можно использовать с первым аргументом без операции. В качестве альтернативы, библиотека Vavr представляет собой набор функциональных оболочек, который немного более согласован, чем Optional, и предоставляет дополнительные полезные оболочки, такие как Try, за счет наличия дополнительной зависимости.

0 голосов
/ 29 мая 2020

Спасибо за комментарии! Я проверил API Vavr Option и Guava Optional.

Guava Optional даже менее богат методами, чем Java Optional, поэтому это не решение.

То, что я нашел, - это метод класса Option Vavr onEmpty , который выполняет Runnable, если значение Option пусто.

SomeValue value = Option.ofOptional(someOptional)
    .onEmpty(() -> log.debug("nothing here"))
    .filter(val -> {
      if (!val.isSpecial()) {
          log.debug("something here, but not special");
          return false;
      }
      return true;
   })
   .getOrElseThrow(NothingHereException::new);

Этот пример отображает одно из отладочных сообщений и выдает исключение только в одном месте. Выглядит намного лучше.

Однако Vavr - большая библиотека с собственными коллекциями, функциональными интерфейсами и монадами, как указано выше. Это большая зависимость для проекта, и вряд ли она будет реализована в моем проекте или любом другом давно существующем проекте только для новой модной Optional замены.

Плюс, Option Вавра делает ' nt имеет что-то вроде onEmptyThrow, так что это не так идеально =).

Эта ветка все еще открыта для предложений, но теперь для обходного пути Java 8, я думаю, этот комментарий может быть считается ответом.

ОБНОВЛЕНИЕ

Похоже, я нашел лучшее решение для подобных проблем. Несколько дней go я обнаружил аннотацию lombok @ExtensionMethod , которая делает возможные решения, подобные ниже

class Extensions {
    public static <T, E extends Throwable> Optional<T> ifNotPresentThrow(Optional<T> opt, Supplier<E> exceptionSupplier) throws E {
        if (!opt.isPresent()) {
            throw exceptionSupplier.get();
        }
        return opt;
    }

}

@ExtensionMethod(Extensions.class)
class Test {
    public static void main(String[] args) {
        // assume there is someNullable variable

        // instead of writing
        Extensions.ifNotPresentThrow(Optional.ofNullable(someNullable), NothingHereException::new)
                .map(...);

        // I can write
        Optional.ofNullable(someNullable)
                .ifNotPresentThrow(NothingHereException::new)
                .map(...); // now possible to continue if optional present
    }
}

С помощью этих Kotlin -подобных методов расширения можно добавить любой метод в Optional API. Lombok просто преобразует второй вариант в первый во время компиляции.

Однако эта функция является экспериментальной (кажется, что навсегда) и имеет возможные ошибки, а декомпилированный байт-код выглядит довольно грязно (с различными ненужными локальными переменными и т.д. c. )

0 голосов
/ 27 мая 2020
SomeValue value = Optional.ofNullable(someValue.isPresent()?       
                   someValue.filter(SomeValue::isSpecial)
                  .orElseThrow(NothingSpecialHereException::new)
                  :null)
                  .orElseThrow(NothingHereException::new) ;

Если присутствует someValue, тогда значение может быть установлено на специальное значение или исключение NothingSpecialHereException иначе, если someValue отсутствует, он выдаст Optinal, он проверит, является ли значение null (someValue отсутствует) Выбросить NothingHereException.

Исключение «NothingSpecialHereException» выбрасывается, только если значение присутствует, а не специальное.

Исключение «NothingHereException» выдается, только если значение не указано.

...