Как я могу выбросить CHECKED исключения из потоков Java 8?

287

Как я могу выбросить CHECKED исключения из потоков / лямбд Java 8?

Другими словами, я хочу сделать такой код:

public List<Class> getClasses() throws ClassNotFoundException {     

    List<Class> classes = 
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
              .map(className -> Class.forName(className))
              .collect(Collectors.toList());                  
    return classes;
    }

Этот код не компилируется, так как Class.forName()метод выше выдает ClassNotFoundException, который проверяется.

Обратите внимание, что я НЕ хочу оборачивать проверенное исключение в исключение времени выполнения и выбрасывать вместо него обернутое непроверенное исключение. Я хочу бросить проверенное исключение и сам , без добавления уродливого try/ catchesв поток.

MarcG
источник
42
Короткий ответ, вы не можете, не нарушая правила, касающиеся исключений. Конечно, вы можете обманывать, и другие с радостью покажут вам, как, но помните, что это обман, и такой обман часто возвращается, чтобы укусить вас. Вы должны поймать исключение и разобраться с ним. Если вы хотите обернуть его, а затем снова выбросить проверенное исключение, вы можете сделать это безопасно.
Брайан Гетц
35
@ Брайан, мне не нужно, чтобы другие говорили мне, как обманывать, я знаю, как обманывать себя, и опубликовал свой способ обмана в ответе ниже, который ты опроверг. Я знаю, что вы участвуете в обсуждении Java, которое решило, что в Streams нет хорошего способа работы с проверенными исключениями, поэтому я нахожу удивительным, что вы заметили мой вопрос, но я разочарован вашим ответом, который просто говорит: «это не хорошо ", не дает никаких оснований для этого, а затем идет одно добавление try / catch снова и снова.
MarcG
22
@Brian, Честно говоря, на практике, когда люди пытаются реорганизовать устаревшие операторы for, половина из них преобразуется в потоки, а другая половина отказывается от рефакторинга, потому что никто не хочет добавлять эти try / catch. Они делают код намного труднее для чтения, конечно, больше, чем оригинальные операторы for. В моем примере кода выше, пока вы поддерживаете «throws ClassNotFoundException», я не вижу никакой разницы с внешним кодом. Не могли бы вы привести примеры из реальной жизни, где это нарушает правила относительно исключений?
MarcG
10
Написание методов-оболочек, которые переносятся в непроверенные исключения, устраняет возражение «беспорядок кода» и не нарушает систему типов. Ответ здесь, который прибегает к «подлому броску» проверенного исключения, действительно нарушает систему типов, потому что вызывающий код не будет ожидать (и не будет позволено перехватить) проверенное исключение.
Брайан Гетц
14
Это не решает проблему, связанную с помехами в коде, потому что тогда вам понадобится вторая попытка / перехват вокруг потока, чтобы развернуть и перебросить исходное исключение. Напротив, если вы выбрасываете проверенное исключение, вам просто нужно сохранить объявление throws ClassNotFoundExceptionв методе, который содержит поток, чтобы вызывающий код ожидал и смог перехватить проверенное исключение.
MarcG

Ответы:

250

Простой ответ на ваш вопрос: вы не можете, по крайней мере, не напрямую. И это не твоя вина. Оракул все испортил. Они цепляются за концепцию проверенных исключений, но непонятно забыли позаботиться о проверенных исключениях при разработке функциональных интерфейсов, потоков, лямбды и т. Д. Все это относится к мельнице экспертов, таких как Роберт С. Мартин, которые называют проверенные исключения неудачным экспериментом.

На мой взгляд, это огромная ошибка в API и небольшая ошибка в спецификации языка .

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

Ошибка в спецификации языка заключается в том, что он не позволяет параметру типа выводить список типов вместо одного типа, если параметр типа используется только в ситуациях, когда список типов допустим ( throwsпункт).

Как программисты на Java, мы ожидаем, что следующий код должен скомпилироваться:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class CheckedStream {
    // List variant to demonstrate what we actually had before refactoring.
    public List<Class> getClasses(final List<String> names) throws ClassNotFoundException {
        final List<Class> classes = new ArrayList<>();
        for (final String name : names)
            classes.add(Class.forName(name));
        return classes;
    }

    // The Stream function which we want to compile.
    public Stream<Class> getClasses(final Stream<String> names) throws ClassNotFoundException {
        return names.map(Class::forName);
    }
}

Тем не менее, это дает:

cher@armor1:~/playground/Java/checkedStream$ javac CheckedStream.java 
CheckedStream.java:13: error: incompatible thrown types ClassNotFoundException in method reference
        return names.map(Class::forName);
                         ^
1 error

Путь , в котором определены функциональные интерфейсы в настоящее время препятствует Compiler от пересылки исключения - нет декларации , которая бы сказать , Stream.map()что если Function.apply() throws E, Stream.map() throws Eкак хорошо.

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

import java.io.IOException;
interface Function<T, R, E extends Throwable> {
    // Declare you throw E, whatever that is.
    R apply(T t) throws E;
}   

interface Stream<T> {
    // Pass through E, whatever mapper defined for E.
    <R, E extends Throwable> Stream<R> map(Function<? super T, ? extends R, E> mapper) throws E;
}   

class Main {
    public static void main(final String... args) throws ClassNotFoundException {
        final Stream<String> s = null;

        // Works: E is ClassNotFoundException.
        s.map(Class::forName);

        // Works: E is RuntimeException (probably).
        s.map(Main::convertClass);

        // Works: E is ClassNotFoundException.
        s.map(Main::throwSome);

        // Doesn't work: E is Exception.
        s.map(Main::throwSomeMore);  // error: unreported exception Exception; must be caught or declared to be thrown
    }   

    public static Class convertClass(final String s) {
        return Main.class;
    }   

    static class FooException extends ClassNotFoundException {}

    static class BarException extends ClassNotFoundException {}

    public static Class throwSome(final String s) throws FooException, BarException {
        throw new FooException();
    }   

    public static Class throwSomeMore(final String s) throws ClassNotFoundException, IOException  {
        throw new FooException();
    }   
}   

В случае, если throwSomeMoreмы хотели бы видеть IOExceptionпропущенный, но это на самом деле не хватает Exception.

Это не идеально, потому что вывод типа, похоже, ищет один тип, даже в случае исключений. Поскольку определение типа необходим один тип, Eдолжно решимость к общему superиз ClassNotFoundExceptionи IOException, что Exception.

Необходима настройка определения типа, чтобы компилятор искал несколько типов, если параметр типа используется там, где допустим список типов ( throwsпункт). Тогда тип исключения, сообщаемый компилятором, будет таким же конкретным, как и исходное throwsобъявление проверенных исключений ссылочного метода, а не одного универсального супертипа.

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

Еще хуже новость заключается в том, что эта тема уже обсуждалась Брайан Гетц в 2010 https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java (новая ссылка: http://mail.openjdk.java.net/pipermail/lambda -dev / 2010-June / 001484.html ) но меня проинформировали, что это расследование в конечном итоге не увенчалось успехом , и что в настоящее время в Oracle нет известных мне работ по смягчению взаимодействия между проверенными исключениями и лямбдами.

Кристиан Худжер
источник
16
Интересный. Я считаю, что некоторые люди ценят потоки за более простой параллельный код, а другие за более чистый код. Брайана Гетца, очевидно, больше заботит параллелизм (поскольку он является автором Java Concurrency на практике), а Роберта Мартина больше заботит чистый код (поскольку он является автором книги «Чистый код»). Попробовать / поймать Boilerplate - это небольшая цена за параллелизм, поэтому неудивительно, что Брайан Гетц не потрясен проблемами использования проверенных исключений внутри потоков. Также неудивительно, что Роберт Мартин ненавидит проверенные исключения, поскольку они добавляют к беспорядку.
MarcG
5
Я предсказываю, что через несколько лет сложность работы с проверенными исключениями внутри потоков приведет к одному из следующих двух результатов: люди просто перестанут использовать проверенные исключения, ИЛИ все начнут использовать какой-то хак, очень похожий на тот, который я опубликовал в мой UtilException ответ. Я бы поспорил, что потоки Java-8 - последний гвоздь в гроб проверенных исключений, если бы не тот факт, что проверенные исключения являются частью JDK. Хотя я люблю и использую проверенные исключения в бизнес-коде (для некоторых конкретных случаев использования), я бы предпочел все распространенные исключения JDK с расширенным временем выполнения.
MarcG
9
@Unihedro Проблема остается в том, что функциональные интерфейсы не пересылают исключения. Мне нужен try-catchблок внутри лямбды, и это просто не имеет никакого смысла. Как только Class.forNameиспользуется каким-либо образом в лямбда, например, в names.forEach(Class::forName), проблема есть. По сути, методы, которые генерируют проверенные исключения, были исключены из участия в функциональном программировании как функциональные интерфейсы напрямую, из-за (плохого!) Дизайна.
Кристиан Худжер
26
@ChristianHujer Исследование «Прозрачность исключений» было только этим - исследование (которое возникло в предложении BGGA). После более глубокого анализа мы обнаружили, что он предлагает плохой баланс стоимости и сложности, и у него были некоторые серьезные проблемы (приводившие к неразрешимым проблемам вывода, и «уловить Х», среди прочего, было несостоятельным.) Очень часто идея языка кажется многообещающим - даже «очевидным» - но после более глубокого исследования оказался ошибочным. Это был один из тех случаев.
Брайан Гетц
13
@BrianGoetz Есть ли общедоступная информация о неразрешимых проблемах умозаключений, о которых вы упомянули? Мне любопытно и хотелось бы это понять.
Кристиан Худжер
169

Этот LambdaExceptionUtilвспомогательный класс позволяет вам использовать любые проверенные исключения в потоках Java, например:

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
      .map(rethrowFunction(Class::forName))
      .collect(Collectors.toList());

Примечание Class::forNameкидает ClassNotFoundException, что проверено . Сам поток также выдает ClassNotFoundException, а НЕ какое-то неконтролируемое исключение.

public final class LambdaExceptionUtil {

@FunctionalInterface
public interface Consumer_WithExceptions<T, E extends Exception> {
    void accept(T t) throws E;
    }

@FunctionalInterface
public interface BiConsumer_WithExceptions<T, U, E extends Exception> {
    void accept(T t, U u) throws E;
    }

@FunctionalInterface
public interface Function_WithExceptions<T, R, E extends Exception> {
    R apply(T t) throws E;
    }

@FunctionalInterface
public interface Supplier_WithExceptions<T, E extends Exception> {
    T get() throws E;
    }

@FunctionalInterface
public interface Runnable_WithExceptions<E extends Exception> {
    void run() throws E;
    }

/** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */
public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
    return t -> {
        try { consumer.accept(t); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> biConsumer) throws E {
    return (t, u) -> {
        try { biConsumer.accept(t, u); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

/** .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName)) */
public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E {
    return t -> {
        try { return function.apply(t); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))), */
public static <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> function) throws E {
    return () -> {
        try { return function.get(); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** uncheck(() -> Class.forName("xxx")); */
public static void uncheck(Runnable_WithExceptions t)
    {
    try { t.run(); }
    catch (Exception exception) { throwAsUnchecked(exception); }
    }

/** uncheck(() -> Class.forName("xxx")); */
public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier)
    {
    try { return supplier.get(); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

/** uncheck(Class::forName, "xxx"); */
public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) {
    try { return function.apply(t); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

@SuppressWarnings ("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; }

}

Много других примеров того, как его использовать (после статического импорта LambdaExceptionUtil):

@Test
public void test_Consumer_with_checked_exceptions() throws IllegalAccessException {
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(className -> System.out.println(Class.forName(className))));

    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(System.out::println));
    }

@Test
public void test_Function_with_checked_exceptions() throws ClassNotFoundException {
    List<Class> classes1
          = Stream.of("Object", "Integer", "String")
                  .map(rethrowFunction(className -> Class.forName("java.lang." + className)))
                  .collect(Collectors.toList());

    List<Class> classes2
          = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                  .map(rethrowFunction(Class::forName))
                  .collect(Collectors.toList());
    }

@Test
public void test_Supplier_with_checked_exceptions() throws ClassNotFoundException {
    Collector.of(
          rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))),
          StringJoiner::add, StringJoiner::merge, StringJoiner::toString);
    }

@Test    
public void test_uncheck_exception_thrown_by_method() {
    Class clazz1 = uncheck(() -> Class.forName("java.lang.String"));

    Class clazz2 = uncheck(Class::forName, "java.lang.String");
    }

@Test (expected = ClassNotFoundException.class)
public void test_if_correct_exception_is_still_thrown_by_method() {
    Class clazz3 = uncheck(Class::forName, "INVALID");
    }    

Примечание 1: В rethrowметодах LambdaExceptionUtilуказанного класса может быть использованы без страха, и OK для использования в любой ситуации . Большое спасибо пользователю @PaoloC, который помог решить последнюю проблему: теперь компилятор попросит вас добавить операторы throw и все так, как если бы вы могли генерировать отмеченные исключения в потоках Java 8.


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

• Вы можете использовать uncheckметоды, если вы вызываете метод, который буквально никогда не может вызвать исключение, которое он объявляет. Например: new String (byteArr, "UTF-8") выбрасывает UnsupportedEncodingException, но UTF-8 гарантируется, что спецификация Java всегда присутствует. Здесь объявление throws является неприятностью, и любое решение заставить его замолчать с минимальными шаблонами приветствуется:String text = uncheck(() -> new String(byteArr, "UTF-8"));

• Вы можете использовать uncheckметоды, если вы реализуете строгий интерфейс, в котором у вас нет возможности добавить объявление throws, и все же бросать исключение вполне уместно. Обтекание исключения только для того, чтобы получить привилегию выбросить его, приводит к трассировке стека с ложными исключениями, которые не дают никакой информации о том, что на самом деле пошло не так. Хорошим примером является Runnable.run (), который не выдает никаких проверенных исключений.

• В любом случае, если вы решите использовать uncheckметоды, имейте в виду следующие 2 последствия выброса исключений CHECKED без условия throws: 1) вызывающий код не сможет перехватить его по имени (если вы попытаетесь, компилятор скажет: исключение никогда не добавляется в тело соответствующего оператора try). Он будет пузыриться и, вероятно, попадет в основной цикл программы с помощью некоторого «catch Exception» или «catch Throwable», что в любом случае может быть тем, что вам нужно. 2) Это нарушает принцип наименьшего удивления: его уже не хватит, чтобы можно было отловить RuntimeExceptionвсе возможные исключения. По этой причине я считаю, что это следует делать не в программном коде, а только в бизнес-коде, который вы полностью контролируете.

MarcG
источник
4
Я чувствую, что этот ответ был несправедливо опущен. Код работает. Проверенные исключения должны быть выброшены или обработаны. Если вы хотите выбросить их, просто оставьте «throws условие» в методе, который содержит поток. Но если вы хотите разобраться с ними, просто упаковав и перебросив, я думаю, что я предпочитаю использовать приведенный выше код, чтобы «разблокировать» исключения и позволить им всплыть самостоятельно. Единственное различие, о котором я знаю, состоит в том, что исключение пузырьков не будет расширять исключение RuntimeException. Я знаю, что пуристам это не понравится, но будет ли это «неизбежно возвращаться, чтобы кого-то укусить»? Не похоже на вероятность.
MarcG
4
@Christian Hujer, честно говоря, он понизил предыдущую версию, прежде чем я добавил объяснение «преимуществ, недостатков и ограничений». Так что, может быть, это было заслужено в то время. Вы не можете научить кого-то, как нарушать правила, по крайней мере, не пытаясь понять и объяснить последствия. Основная причина, по которой я разместил этот вопрос, заключалась в том, чтобы получить обратную связь по поводу недостатков моего ответа. Я получил этот отзыв не здесь, а от другого вопроса в programmers.stackexchange. Затем я вернулся сюда и обновил свой ответ.
MarcG
16
Я понизил голосование только потому, что это поощряет не поддерживаемый код . Это уродливый взлом, хотя и умный, и я никогда не найду этот ответ полезным. Это, опять же, еще один «не использовать» языка.
Unihedron
12
@Unihedro, но почему это стало неприемлемым? Я не понимаю почему. Есть примеры?
MarcG
2
На мой взгляд, @SuppressWarnings ("unchecked")хитрость компилятора совершенно неприемлема.
Турбьерн Равн Андерсен
26

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

Вот немного более безопасный способ сделать это (но я все еще не рекомендую это.)

class WrappedException extends RuntimeException {
    Throwable cause;

    WrappedException(Throwable cause) { this.cause = cause; }
}

static WrappedException throwWrapped(Throwable t) {
    throw new WrappedException(t);
}

try 
    source.stream()
          .filter(e -> { ... try { ... } catch (IOException e) { throwWrapped(e); } ... })
          ...
}
catch (WrappedException w) {
    throw (IOException) w.cause;
}

Здесь вы перехватываете исключение в лямбда-выражении, выбрасываете сигнал из потокового конвейера, который указывает на то, что вычисления не удалось, перехватывает сигнал, и действует на этот сигнал, чтобы вызвать основное исключение. Ключевым моментом является то, что вы всегда перехватываете синтетическое исключение, а не допускаете утечки проверенного исключения, не объявляя об этом исключении.

Брайан Гетц
источник
18
Просто вопрос; каково было дизайнерское решение, которое привело к тому, что лямбды не могли распространять проверенные исключения вне их контекста? Обратите внимание, что я понимаю, что функциональные интерфейсы, такие как Functionetc, ничего не делают throws; Мне просто любопытно.
fge
4
Это throw w.cause;не сделало бы компилятор жалуется , что метод не бросает и не поймать Throwable? Таким образом, вполне вероятно, что там IOExceptionбудет необходим актерский состав . Кроме того, если лямбда выдает более одного типа проверенных исключений, тело улова станет несколько уродливым с некоторыми instanceofпроверками (или чем-то еще с аналогичной целью), чтобы проверить, какое проверенное исключение было выброшено.
Виктор Стафуса
10
@schatten Одна из причин заключается в том, что вы можете забыть поймать МЫ, и тогда странное исключение (с которым никто не знает, как справиться) утечет. (Вы можете сказать «но вы поймали исключение, поэтому оно безопасно». В этом игрушечном примере. Но каждый раз, когда я видел кодовую базу, применяющую этот подход, в конце концов кто-то забывает. Искушение игнорировать исключения не знает границ.) Еще один риск заключается в том, что его использование безопасно для конкретной комбинации (использование сайта, исключения). Это не подходит для нескольких исключений или негомогенного использования.
Брайан Гетц
2
@hoodaticus Я согласен с тобой. Сказал, что вы предпочитаете упаковывать все больше и больше (как показано выше, увеличивая риск «забыть») или просто создавать 4 умных интерфейса и использовать лямбды без упаковки, как показано в stackoverflow.com/a/30974991/2365724 ? Спасибо
PaoloC
10
Честно говоря, это решение просто совершенно неработоспособно. Я думал, что целью потоков было уменьшить шаблон, а не увеличить его.
wvdz
24

Ты можешь!

Расширяя @marcg UtilExceptionи добавляя, throw Eгде необходимо: таким образом, компилятор попросит вас добавить предложения throw и все так, как если бы вы могли изначально генерировать отмеченные исключения в потоках java 8.

Инструкции: просто скопируйте / вставьте LambdaExceptionUtilв вашу IDE, а затем используйте его, как показано ниже LambdaExceptionUtilTest.

public final class LambdaExceptionUtil {

    @FunctionalInterface
    public interface Consumer_WithExceptions<T, E extends Exception> {
        void accept(T t) throws E;
    }

    @FunctionalInterface
    public interface Function_WithExceptions<T, R, E extends Exception> {
        R apply(T t) throws E;
    }

    /**
     * .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name))));
     */
    public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
        return t -> {
            try {
                consumer.accept(t);
            } catch (Exception exception) {
                throwActualException(exception);
            }
        };
    }

    /**
     * .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName))
     */
    public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E  {
        return t -> {
            try {
                return function.apply(t);
            } catch (Exception exception) {
                throwActualException(exception);
                return null;
            }
        };
    }

    @SuppressWarnings("unchecked")
    private static <E extends Exception> void throwActualException(Exception exception) throws E {
        throw (E) exception;
    }

}

Некоторый тест, чтобы показать использование и поведение:

public class LambdaExceptionUtilTest {

    @Test(expected = MyTestException.class)
    public void testConsumer() throws MyTestException {
        Stream.of((String)null).forEach(rethrowConsumer(s -> checkValue(s)));
    }

    private void checkValue(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
    }

    private class MyTestException extends Exception { }

    @Test
    public void testConsumerRaisingExceptionInTheMiddle() {
        MyLongAccumulator accumulator = new MyLongAccumulator();
        try {
            Stream.of(2L, 3L, 4L, null, 5L).forEach(rethrowConsumer(s -> accumulator.add(s)));
            fail();
        } catch (MyTestException e) {
            assertEquals(9L, accumulator.acc);
        }
    }

    private class MyLongAccumulator {
        private long acc = 0;
        public void add(Long value) throws MyTestException {
            if(value==null) {
                throw new MyTestException();
            }
            acc += value;
        }
    }

    @Test
    public void testFunction() throws MyTestException {
        List<Integer> sizes = Stream.of("ciao", "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
        assertEquals(2, sizes.size());
        assertEquals(4, sizes.get(0).intValue());
        assertEquals(5, sizes.get(1).intValue());
    }

    private Integer transform(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
        return value.length();
    }

    @Test(expected = MyTestException.class)
    public void testFunctionRaisingException() throws MyTestException {
        Stream.of("ciao", null, "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
    }

}
PaoloC
источник
1
Извините @setheron, вы правы, просто добавьте <Integer>раньше map. Фактически, компилятор Java не может определить Integerтип возвращаемого значения. Все остальное должно быть правильно.
PaoloC
1
Это сработало для меня. Это сделало ответ MarcG совершенным, заставив обработать исключение.
Skychan
1
Решение вышеуказанной проблемы: объявите переменную следующим образом: Consumer <ThingType> expression = rethrowConsumer ((ThingType thing) -> thing.clone ()); затем используйте это выражение во внутреннем foreach.
Skychan
1
@Skychan: Так как в этой модифицированной новой версии вы больше не исключаете никаких исключений, вероятно, это немного сложнее для системы логического вывода. В некоторых комментариях ниже Брайан Гетц говорит о «прозрачности исключений», ведущей к «неразрешимым проблемам вывода».
MarcG
3
Очень хорошо. Единственное, к сожалению, то, что он не работает идеально с методом, который выдает несколько проверенных исключений. В этом случае компилятор заставит вас поймать общий супертип, например Exception.
wvdz
12

Просто используйте любой из NoException (мой проект), jOOλ's Unchecked , throwing- lambdas , Throwable интерфейсов или Faux Pas .

// NoException
stream.map(Exceptions.sneak().function(Class::forName));

// jOOλ
stream.map(Unchecked.function(Class::forName));

// throwing-lambdas
stream.map(Throwing.function(Class::forName).sneakyThrow());

// Throwable interfaces
stream.map(FunctionWithThrowable.aFunctionThatUnsafelyThrowsUnchecked(Class::forName));

// Faux Pas
stream.map(FauxPas.throwingFunction(Class::forName));
Роберт Важан
источник
7

Я написал библиотеку, которая расширяет Stream API, чтобы вы могли генерировать проверенные исключения. Он использует трюк Брайана Гетца.

Ваш код станет

public List<Class> getClasses() throws ClassNotFoundException {     
    Stream<String> classNames = 
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String");

    return ThrowingStream.of(classNames, ClassNotFoundException.class)
               .map(Class::forName)
               .collect(Collectors.toList());
}
Джеффри
источник
7

Этот ответ похож на 17, но избегая определения исключений оболочки:

List test = new ArrayList();
        try {
            test.forEach(obj -> {

                //let say some functionality throws an exception
                try {
                    throw new IOException("test");
                }
                catch(Exception e) {
                    throw new RuntimeException(e);
                }
            });
        }
        catch (RuntimeException re) {
            if(re.getCause() instanceof IOException) {
                //do your logic for catching checked
            }
            else 
                throw re; // it might be that there is real runtime exception
        }
Радослав Стоянов
источник
1
Это простое и эффективное решение.
Лин W
2
Это именно то, чего Оп не хотел: пробовать блоки в лямбде. Более того, он работает только так, как ожидалось, если никакой другой код вне блока try не помещает IOException в RuntimeException. Чтобы избежать этого, можно использовать настраиваемое исключение wrapper-RuntimeException (определенное как закрытый внутренний класс).
Малте Хартвиг,
5

Ты не можешь.

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

В вашем случае вы сможете сделать это:

import static com.github.fge.lambdas.functions.Functions.wrap;

final ThrowingFunction<String, Class<?>> f = wrap(Class::forName);

List<Class> classes =
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .map(f.orThrow(MyException.class))
          .collect(Collectors.toList());

и поймать MyException.

Это один пример. Другим примером является то, что вы можете использовать .orReturn()значение по умолчанию.

Обратите внимание, что это все еще в стадии разработки, еще не все. Лучшие имена, больше возможностей и т.д.

FGE
источник
2
Но затем, если вы хотите выбросить исходное проверенное исключение, вам нужно будет добавить try / catch вокруг потока, чтобы развернуть его, что по-прежнему ужасно! Мне нравится идея, что вы МОЖЕТЕ бросить непроверенное исключение, если хотите, и что вы МОЖЕТЕ вернуть значение по умолчанию в поток, если хотите, но я также думаю, что вы должны добавить какой-то .orThrowChecked()метод в ваш проект, который позволяет выбрасывать само проверенное исключение , Пожалуйста, посмотрите на мой UtilExceptionответ на этой странице и посмотрите, нравится ли вам идея добавить эту третью возможность в ваш проект.
MarcG
«Но затем, если вы хотите выбросить исходное проверенное исключение, вам нужно будет добавить try / catch вокруг потока, чтобы развернуть его, что по-прежнему ужасно!» <- да, но у тебя нет выбора. Лямбда не могут распространяться проверяемые исключения из контекста, это дизайн «решение» (я его просмотра как недостаток, лично, но ohwell)
FGE
Что касается вашей идеи, я не очень хорошо понимаю, что она делает, извините; В конце концов, вы все равно бросаете как непроверенный, так как это отличается от того, что я делаю? (кроме того, что у меня есть другой интерфейс для этого)
fge
В любом случае, вы можете внести свой вклад в проект! Кроме того, вы заметили, что Streamреализует AutoCloseable?
fge
Позвольте мне спросить вас следующее: MyExceptionдолжно ли ваше вышеупомянутое быть непроверенным исключением?
MarcG
3

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

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .map(Try.<String, Class<?>>safe(Class::forName)
                  .handle(System.out::println)
                  .unsafe())
          .collect(toList());

Код ниже демонстрирует это для интерфейсов Consumer, Supplier и Function. Это может быть легко расширено. Некоторые публичные ключевые слова были удалены для этого примера.

Класс Try является конечной точкой для клиентского кода. Безопасные методы могут иметь уникальное имя для каждого типа функции. CheckedConsumer , CheckedSupplier и CheckedFunction являются проверенными аналогами функций lib, которые можно использовать независимо от Try

CheckedBuilder - это интерфейс для обработки исключений в некоторых проверенных функциях. orTry позволяет выполнить другую функцию того же типа, если предыдущая была неудачной. handle обеспечивает обработку исключений, включая фильтрацию типов исключений. Порядок обработчиков важен. Сокращение методов небезопасно и повторное отбрасывание последнего исключения в цепочке выполнения. Методы редукции orElse и orElseGet возвращают альтернативные значения, такие как Необязательные, если все функции не выполнены. Также есть метод подавления . CheckedWrapper является распространенной реализацией CheckedBuilder.

final class Try {

    public static <T> CheckedBuilder<Supplier<T>, CheckedSupplier<T>, T> 
        safe(CheckedSupplier<T> supplier) {
        return new CheckedWrapper<>(supplier, 
                (current, next, handler, orResult) -> () -> {
            try { return current.get(); } catch (Exception ex) {
                handler.accept(ex);
                return next.isPresent() ? next.get().get() : orResult.apply(ex);
            }
        });
    }

    public static <T> Supplier<T> unsafe(CheckedSupplier<T> supplier) {
        return supplier;
    }

    public static <T> CheckedBuilder<Consumer<T>, CheckedConsumer<T>, Void> 
        safe(CheckedConsumer<T> consumer) {
        return new CheckedWrapper<>(consumer, 
                (current, next, handler, orResult) -> t -> {
            try { current.accept(t); } catch (Exception ex) {
                handler.accept(ex);
                if (next.isPresent()) {
                    next.get().accept(t);
                } else {
                    orResult.apply(ex);
                }
            }
        });
    }

    public static <T> Consumer<T> unsafe(CheckedConsumer<T> consumer) {
        return consumer;
    }

    public static <T, R> CheckedBuilder<Function<T, R>, CheckedFunction<T, R>, R> 
        safe(CheckedFunction<T, R> function) {
        return new CheckedWrapper<>(function, 
                (current, next, handler, orResult) -> t -> {
            try { return current.applyUnsafe(t); } catch (Exception ex) {
                handler.accept(ex);
                return next.isPresent() ? next.get().apply(t) : orResult.apply(ex);
            }
        });
    }

    public static <T, R> Function<T, R> unsafe(CheckedFunction<T, R> function) {
        return function;
    }

    @SuppressWarnings ("unchecked")
    static <T, E extends Throwable> T throwAsUnchecked(Throwable exception) throws E { 
        throw (E) exception; 
    }
}

@FunctionalInterface interface CheckedConsumer<T> extends Consumer<T> {
    void acceptUnsafe(T t) throws Exception;
    @Override default void accept(T t) {
        try { acceptUnsafe(t); } catch (Exception ex) {
            Try.throwAsUnchecked(ex);
        }
    }
}

@FunctionalInterface interface CheckedFunction<T, R> extends Function<T, R> {
    R applyUnsafe(T t) throws Exception;
    @Override default R apply(T t) {
        try { return applyUnsafe(t); } catch (Exception ex) {
            return Try.throwAsUnchecked(ex);
        }
    }
}

@FunctionalInterface interface CheckedSupplier<T> extends Supplier<T> {
    T getUnsafe() throws Exception;
    @Override default T get() {
        try { return getUnsafe(); } catch (Exception ex) {
            return Try.throwAsUnchecked(ex);
        }
    }
}

interface ReduceFunction<TSafe, TUnsafe, R> {
    TSafe wrap(TUnsafe current, Optional<TSafe> next, 
            Consumer<Throwable> handler, Function<Throwable, R> orResult);
}

interface CheckedBuilder<TSafe, TUnsafe, R> {
    CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next);

    CheckedBuilder<TSafe, TUnsafe, R> handle(Consumer<Throwable> handler);

    <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handle(
            Class<E> exceptionType, Consumer<E> handler);

    CheckedBuilder<TSafe, TUnsafe, R> handleLast(Consumer<Throwable> handler);

    <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handleLast(
            Class<E> exceptionType, Consumer<? super E> handler);

    TSafe unsafe();
    TSafe rethrow(Function<Throwable, Exception> transformer);
    TSafe suppress();
    TSafe orElse(R value);
    TSafe orElseGet(Supplier<R> valueProvider);
}

final class CheckedWrapper<TSafe, TUnsafe, R> 
        implements CheckedBuilder<TSafe, TUnsafe, R> {

    private final TUnsafe function;
    private final ReduceFunction<TSafe, TUnsafe, R> reduceFunction;

    private final CheckedWrapper<TSafe, TUnsafe, R> root;
    private CheckedWrapper<TSafe, TUnsafe, R> next;

    private Consumer<Throwable> handlers = ex -> { };
    private Consumer<Throwable> lastHandlers = ex -> { };

    CheckedWrapper(TUnsafe function, 
            ReduceFunction<TSafe, TUnsafe, R> reduceFunction) {
        this.function = function;
        this.reduceFunction = reduceFunction;
        this.root = this;
    }

    private CheckedWrapper(TUnsafe function, 
            CheckedWrapper<TSafe, TUnsafe, R> prev) {
        this.function = function;
        this.reduceFunction = prev.reduceFunction;
        this.root = prev.root;
        prev.next = this;
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next) {
        return new CheckedWrapper<>(next, this);
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> handle(
            Consumer<Throwable> handler) {
        handlers = handlers.andThen(handler);
        return this;
    }

    @Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> 
        handle(Class<E> exceptionType, Consumer<E> handler) {
        handlers = handlers.andThen(ex -> {
            if (exceptionType.isInstance(ex)) {
                handler.accept(exceptionType.cast(ex));
            }
        });
        return this;
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> handleLast(
            Consumer<Throwable> handler) {
        lastHandlers = lastHandlers.andThen(handler);
        return this;
    }

    @Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> 
        handleLast(Class<E> exceptionType, Consumer<? super E> handler) {
        lastHandlers = lastHandlers.andThen(ex -> {
            if (exceptionType.isInstance(ex)) {
                handler.accept(exceptionType.cast(ex));
            }
        });
        return this;
    }

    @Override public TSafe unsafe() {
        return root.reduce(ex -> Try.throwAsUnchecked(ex));
    }

    @Override
    public TSafe rethrow(Function<Throwable, Exception> transformer) {
        return root.reduce(ex -> Try.throwAsUnchecked(transformer.apply(ex)));
    }

    @Override public TSafe suppress() {
        return root.reduce(ex -> null);
    }

    @Override public TSafe orElse(R value) {
        return root.reduce(ex -> value);
    }

    @Override public TSafe orElseGet(Supplier<R> valueProvider) {
        Objects.requireNonNull(valueProvider);
        return root.reduce(ex -> valueProvider.get());
    }

    private TSafe reduce(Function<Throwable, R> orResult) {
        return reduceFunction.wrap(function, 
                Optional.ofNullable(next).map(p -> p.reduce(orResult)), 
                this::handle, orResult);
    }

    private void handle(Throwable ex) {
        for (CheckedWrapper<TSafe, TUnsafe, R> current = this; 
                current != null; 
                current = current.next) {
            current.handlers.accept(ex);
        }
        lastHandlers.accept(ex);
    }
}
интроспектированные
источник
3

TL; DR Просто используйте Ломбок@SneakyThrows .

Кристиан Худжер уже подробно объяснил, почему выбрасывание проверенных исключений из потока, строго говоря, невозможно из-за ограничений Java.

В некоторых других ответах объясняются хитрости, позволяющие обойти ограничения языка, но при этом он способен выполнить требование «самого проверенного исключения и без добавления уродливых try / catch для потока» , некоторые из них требуют десятков дополнительных строк. шаблонной.

Я собираюсь выделить другой вариант для этого, который ИМХО намного чище, чем все остальные: Ломбок @SneakyThrows. Это было упомянуто мимоходом другими ответами, но было немного погребено под множеством ненужных подробностей.

Полученный код так же прост, как:

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes =
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                .map(className -> getClass(className))
                .collect(Collectors.toList());
    return classes;
}

@SneakyThrows                                 // <= this is the only new code
private Class<?> getClass(String className) {
    return Class.forName(className);
}

Нам просто потребовался один Extract Methodрефакторинг (выполненный IDE) и еще одна строка для @SneakyThrows. Аннотация заботится о добавлении всего шаблона, чтобы убедиться, что вы можете выбросить проверенное исключение, не оборачивая его RuntimeExceptionи не требуя явного его объявления.

sergut
источник
4
Использование ломбок не должно поощряться.
Драгас
2

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

private void run() {
    List<String> list = Stream.of(1, 2, 3, 4).map(wrapper(i ->
            String.valueOf(++i / 0), i -> String.valueOf(++i))).collect(Collectors.toList());
    System.out.println(list.toString());
}

private <T, R, E extends Exception> Function<T, R> wrapper(ThrowingFunction<T, R, E> function, 
Function<T, R> onException) {
    return i -> {
        try {
            return function.apply(i);
        } catch (ArithmeticException e) {
            System.out.println("Exception: " + i);
            return onException.apply(i);
        } catch (Exception e) {
            System.out.println("Other: " + i);
            return onException.apply(i);
        }
    };
}

@FunctionalInterface
interface ThrowingFunction<T, R, E extends Exception> {
    R apply(T t) throws E;
}
Петр Невинский
источник
2

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

    @Test
    public void getClasses() {

        String[] classNames = {"java.lang.Object", "java.lang.Integer", "java.lang.Foo"};
        List<Class> classes =
                Stream.of(classNames)
                        .map(className -> {
                            try {
                                return Class.forName(className);
                            } catch (ClassNotFoundException e) {
                                // log the error
                                return null;
                            }
                        })
                        .filter(c -> c != null)
                        .collect(Collectors.toList());

        if (classes.size() != classNames.length) {
            // add your error handling here if needed or process only the resulting list
            System.out.println("Did not process all class names");
        }

        classes.forEach(System.out::println);
    }
OSGI Java
источник
1

Я согласен с комментариями выше, при использовании Stream.map вы ограничены реализацией функции, которая не генерирует исключения.

Однако вы можете создать свой собственный FunctionalInterface, который выдает, как показано ниже.

@FunctionalInterface
public interface UseInstance<T, X extends Throwable> {
  void accept(T instance) throws X;
}

затем реализуйте его, используя Lambdas или ссылки, как показано ниже.

import java.io.FileWriter;
import java.io.IOException;

//lambda expressions and the execute around method (EAM) pattern to
//manage resources

public class FileWriterEAM  {
  private final FileWriter writer;

  private FileWriterEAM(final String fileName) throws IOException {
    writer = new FileWriter(fileName);
  }
  private void close() throws IOException {
    System.out.println("close called automatically...");
    writer.close();
  }
  public void writeStuff(final String message) throws IOException {
    writer.write(message);
  }
  //...

  public static void use(final String fileName, final UseInstance<FileWriterEAM, IOException> block) throws IOException {

    final FileWriterEAM writerEAM = new FileWriterEAM(fileName);    
    try {
      block.accept(writerEAM);
    } finally {
      writerEAM.close();
    }
  }

  public static void main(final String[] args) throws IOException {

    FileWriterEAM.use("eam.txt", writerEAM -> writerEAM.writeStuff("sweet"));

    FileWriterEAM.use("eam2.txt", writerEAM -> {
        writerEAM.writeStuff("how");
        writerEAM.writeStuff("sweet");      
      });

    FileWriterEAM.use("eam3.txt", FileWriterEAM::writeIt);     

  }


 void writeIt() throws IOException{
     this.writeStuff("How ");
     this.writeStuff("sweet ");
     this.writeStuff("it is");

 }

}
JohnnyO
источник
1

Единственный встроенный способ обработки проверенных исключений, которые могут быть сгенерированы mapоперацией, - это инкапсулировать их в CompletableFuture. (Это Optionalболее простая альтернатива, если вам не нужно сохранять исключение.) Эти классы предназначены для того, чтобы вы могли функционально представлять условные операции.

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

    CompletableFuture<List<Class<?>>> classes =
            Stream.of("java.lang.String", "java.lang.Integer", "java.lang.Double")
                  .map(MonadUtils.applyOrDie(Class::forName))
                  .map(cfc -> cfc.thenApply(Class::getSuperclass))
                  .collect(MonadUtils.cfCollector(ArrayList::new,
                                                  List::add,
                                                  (List<Class<?>> l1, List<Class<?>> l2) -> { l1.addAll(l2); return l1; },
                                                  x -> x));
    classes.thenAccept(System.out::println)
           .exceptionally(t -> { System.out.println("unable to get class: " + t); return null; });

Это производит следующий вывод:

[class java.lang.Object, class java.lang.Number, class java.lang.Number]

applyOrDieМетод принимает , Functionчто бросает исключение, и преобразует его в Functionтом , что возвращает уже завершено CompletableFuture- либо завершено , как правило , с результатом исходной функции, либо завершается исключительно с брошенным исключением.

Вторая mapоперация показывает, что теперь у вас есть Stream<CompletableFuture<T>>вместо просто Stream<T>. CompletableFutureвыполняет только эту операцию, если вышестоящая операция выполнена успешно. API делает это объяснением, но относительно безболезненным.

Пока вы не дойдете до collectфазы, то есть. Вот где нам требуется довольно значительный вспомогательный метод. Мы хотим , чтобы «поднять» нормальную работу коллекции (в данном случае toList()) «внутри» CompletableFuture- cfCollector()позволяют нам сделать это с помощью supplier, accumulator, combiner, и finisherчто не нужно знать вообще ничего о CompletableFuture.

Вспомогательные методы можно найти на GitHub в моем MonadUtilsклассе, который все еще находится в стадии разработки.

Мэтт МакГенри
источник
1

Вероятно, лучший и более функциональный способ - обернуть исключения и распространять их дальше в потоке. Взгляните, например, на тип Try Vavr .

Пример:

interface CheckedFunction<I, O> {
    O apply(I i) throws Exception; }

static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
    return i -> {
        try {
            return f.apply(i);
        } catch(Exception ex) {

            throw new RuntimeException(ex);
        }
    } }

fileNamesToRead.map(unchecked(file -> Files.readAllLines(file)))

ИЛИ

@SuppressWarnings("unchecked")
private static <T, E extends Exception> T throwUnchecked(Exception e) throws E {
    throw (E) e;
}

static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
    return arg -> {
        try {
            return f.apply(arg);
        } catch(Exception ex) {
            return throwUnchecked(ex);
        }
    };
}

2-я реализация избегает включения исключения в RuntimeException. throwUncheckedработает, потому что почти всегда все общие исключения рассматриваются как непроверенные в Java.

Михаил Холодков
источник
1

Я использую этот вид исключения упаковки:

public class CheckedExceptionWrapper extends RuntimeException {
    ...
    public <T extends Exception> CheckedExceptionWrapper rethrow() throws T {
        throw (T) getCause();
    }
}

Это потребует обработки этих исключений статически:

void method() throws IOException, ServletException {
    try { 
        list.stream().forEach(object -> {
            ...
            throw new CheckedExceptionWrapper(e);
            ...            
        });
    } catch (CheckedExceptionWrapper e){
        e.<IOException>rethrow();
        e.<ServletExcepion>rethrow();
    }
}

Попробуйте онлайн!

Хотя исключение в любом случае будет переброшено во время первого rethrow()вызова (о, Java-дженерики ...), этот способ позволяет получить строгое статическое определение возможных исключений (требуется их объявить в throws). И нет instanceofили что-то нужно.

Тарас
источник
-1

Я думаю, что этот подход является правильным:

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes;
    try {
        classes = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String").map(className -> {
            try {
                return Class.forName(className);
            } catch (ClassNotFoundException e) {
                throw new UndeclaredThrowableException(e);
            }
        }).collect(Collectors.toList());
    } catch (UndeclaredThrowableException e) {
        if (e.getCause() instanceof ClassNotFoundException) {
            throw (ClassNotFoundException) e.getCause();
        } else {
            // this should never happen
            throw new IllegalStateException(e.getMessage(), e);
        }
    }
    return classes;
}

Оборачивание проверенного исключения внутри Callablein UndeclaredThrowableException(это вариант использования для этого исключения) и развертывание его снаружи.

Да, я нахожу это уродливым, и я бы посоветовал не использовать лямбды в этом случае и просто вернуться к старому доброму циклу, если только вы не работаете с параллельным потоком, и паралеллизация приносит объективное преимущество, которое оправдывает нечитаемость кода.

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

Матиас Ронге
источник
1
(1) Уже есть несколько ответов, показывающих пример, подобный этому, так что ваш ответ добавляет к вопросам и ответам, которые еще не были рассмотрены? Публикация повторяющихся ответов, как это, просто добавляет беспорядок на сайт. (2) ОП специально говорит, что не хочет этого делать. «Обратите внимание, что я НЕ хочу оборачивать проверенное исключение в исключение времени выполнения и вместо этого выбрасывать завернутое непроверенное исключение»
Radiodef