Рубрики
Без рубрики

Исключения и потоки

Об авторе: Виктор – Чемпион Java, опытный Независимый тренер, оратор и основатель… Помеченный java, функциональный, чистый код.

Об авторе: Виктор – Чемпион Java, опытный Независимый тренер, спикер и основатель огромного сообщества разработчиков. Подробнее о виктор rentea.ro

Эта статья была впервые опубликована на Блог Виктора

Java 8 дала нам Необязательно , мощное оружие против наиболее частого исключения в Java: Исключение NullPointerException . К сожалению, Java 8 также принесла новые головные боли, связанные с исключениями, поскольку функциональные интерфейсы по умолчанию в Java 8 не объявляют о создании каких-либо проверенных исключений. Поэтому каждый раз, когда вы получаете проверенное исключение в лямбде, вы должны это как-то исправить.

Преобразование проверенных исключений в исключения во время выполнения

Предложение по умолчанию, предлагаемое большинством IDE для автоматического устранения этой проблемы, приведет к созданию кода, подобного этому:

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
List dateList = asList("2020-10-11", "2020-nov-12", "2020-12-01");
List dates = dateList.stream().map(s -> {
   try {
      return format.parse(s);
   } catch (ParseException e) {
      throw new RuntimeException(e);
   }
}).collect(toList());

Ужасный код.

Мы могли бы создать специальную функцию, выполняющую только .синтаксический анализ а затем наложите на него заклинание с помощью @SneakyThrows , как мы обсуждали в предыдущей статье :

   List dates = dateList.stream()
        .map(s -> uglyParse(format, s))
        .collect(toList());
   ...
}

@SneakyThrows
private static Date uglyParse(SimpleDateFormat format, String s) {
   return format.parse(s);
}

Но создание этого нового метода только для того, чтобы взломать его с помощью Ломбока, кажется неправильным. Действительно, мы создали его по чисто технической причине: чтобы скрыть раздражающее проверенное исключение, которое не соответствует java.util. Функция интерфейс, который не объявляет, что нужно что-то выбрасывать.

Давайте немного поиграем и создадим Выбрасывающая функция интерфейс, объявляющий о выбрасывании любого проверенного исключения:

interface ThrowingFunction {
    R apply(T t) throws Exception;
}

Затем наше выражение s->format.parse(ы) может быть целевым типом для этого нового интерфейса, поэтому следующая строка компилируется:

ThrowingFunction p = s -> format.parse(s);
// or
ThrowingFunction p = format::parse;

К сожалению, операция Stream.map() требует java.util. Функция , вы не можете это изменить. Но давайте представим, что у нас есть функция, которая будет принимать Функция метания и верните обратно “классическую” Функцию , которая больше не выдает никаких проверенных исключений.

Function f = wrapAsRuntime(p);
List dates = dateList.stream().map(f).collect(toList());

И вот странная функция перенос во время выполнения :

private static  Function wrapAsRuntime(ThrowingFunction p) {
    return t -> {
        try {
            return p.apply(t);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    };
}

Если для вас это полная чушь, то я бы посоветовал вам попробовать напечатать ее самостоятельно. Это очень помогает!

Обратите внимание, что мы использовали дженерики, чтобы сделать его многоразовым. Это довольно хорошая идея, не так ли? Это так хорошо, что, конечно, у других это было много лет назад …:)

Вводим Непроверенный.функция() из библиотеки jool , которая делает ИМЕННО то, что мы сделали выше. Используя его, окончательный код выглядит так:

List dates = dateList.stream().map(Unchecked.function(format::parse)).collect(toList());

Если вы используете Java 8 уже много лет, то эта библиотека просто необходима.

Наилучшая практика : Всякий раз, когда проверенные исключения раздражают вас в лямбдах -> или ссылках на методы :: , используйте Непроверенный. * чтобы перестроить его как Исключение времени выполнения

Это не связано с каким-либо взломом байт-кода ( как это делает @ SneakyThrows ), а только с простым кодом java. Передача функции в качестве параметра другой функции – очень полезная практика, о которой я скоро напишу в блоге, но функции, которые одновременно принимают и возвращают функции, мне не нравятся. Это один из самых сложных, трудных для чтения и особенно трудных для отладки в Java. Но так как это делает библиотека, и цель очевидна, я никогда не колебался использовать ее во многих своих проектах.

Теперь давайте немного изменим перспективу. Как бы вы его ни крутили, обработка всего потока останавливается при возникновении первого исключения. Но что, если мы не хотим аварийно завершать работу, а вместо этого собираем все ошибки?

Монада Попытки

Давайте немного изменим требования: теперь мы хотим проанализировать все действительные даты и вернуть их, ЕСЛИ хотя бы половина из них поддается анализу, в противном случае мы должны выдать исключение. На этот раз мы не можем позволить исключению прервать выполнение нашего потока. Вместо этого мы хотим просмотреть все элементы и собрать как проанализированные даты, так и исключения. Например, если нам даны 3 правильно отформатированные даты и 2 неверные, мы должны вернуть 3 даты, которые мы смогли правильно проанализировать.

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

Класс Try<> из библиотеки vavr является специализацией концепции Либо<> , присутствующей во многих функциональных языках программирования. Экземпляр может хранить либо результат, либо возникшее исключение (если таковое имеется).

List> tries = dateList.stream()
    .map(s -> Try.of(
        () -> format.parse(s) // throwing code
    ))
    .collect(toList());

Если вызывающий код завершается сбоем с исключением, окружающая функция Try.of перехватит это исключение и вернет неудачную попытку . Поэтому в приведенном выше списке попыток могут быть элементы с isSuccess() либо true или ложь . Чтобы подсчитать коэффициент успеха, самая короткая (самая вызывающая) форма – это:

double successRatio = tries.stream()
    .mapToInt(t -> t.isSuccess() ? 1 : 0)
    .average()
    .orElse(0);

Затем,

if (successRatio > .5) {
    return tries.stream()
        .filter(Try::isSuccess)
        .map(Try::get)
        .collect(toList());
} else {
    throw new IllegalArgumentException("Too many invalid dates");
}

Проблема решена.

Чтобы лучше понять код, мы можем извлечь из него функцию, которая возвращает Попробуйте<> :

private static Try tryParse(SimpleDateFormat format, String s) {
    return Try.of(() -> format.parse(s));
}

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

Кстати, если вы немного подумаете, вы могли бы решить проблему без Попробуйте , дважды проверив данные: сначала для подсчета анализируемых дат, а затем для их фактического анализа. Или даже один проход с использованием комбинации .карта возвращающий нуль / возвращающий нуль / для ошибок, за которыми следует

Совет : Рассмотрите * вавр. Попробуйте<> когда вы хотите собрать как результаты, так и исключения за один проход через данные.

Кстати, если вы продолжаете думать о слове “Монада”, вот хорошая статья, которая поможет вам преодолеть это: Монады для разработчиков Java

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

Выводы

  • Проверенные исключения плохо сочетаются с API Java Stream.
  • Использовать @SneakyThrows ( Ломбок ) или Непроверенный ( jOOL ), чтобы избавиться от проверенных исключений с потоками
  • Рассмотреть Попробуйте ( ( va

Оригинал: “https://dev.to/victorrentea/exceptions-and-streams-3m5i”