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

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

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

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 в поток.

15 ответов


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

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

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

ошибка в спецификации языка заключается в том, что она не позволяет параметр типа вывести список типы вместо одного типа, если параметр type используется только в ситуациях, когда список типов разрешим (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

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

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

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 of ClassNotFoundException и IOException, которая составляет Exception.

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

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

еще хуже то, что эта тема уже обсуждалась Брайаном Гетцем в 2010 году https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java и кажется, что эта проблема была просто проигнорирована, поэтому мне интересно, что делает Oracle.


этой 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 streams.


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

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

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

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


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

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

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

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


вы можете!

расширение @marcg ' s 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());
    }

}

просто используйте любой из NoException (мой проект), jOOλ это!--3-->, метания-лямбда, 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));

я писал библиотека Это расширяет 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());
}

этот ответ похож на 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
        }

нельзя.

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

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

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() некоторое значение по умолчанию.

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


суммирование комментариев выше расширенное решение заключается в использовании специальной оболочки для непроверенных функций с построителем, таким как 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());

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

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

CheckedBuilder - это интерфейс для обработки исключений в некоторых проверили функцию. orTry позволяет выполнить другую функцию того же типа, Если предыдущая не удалась. дескриптор обеспечивает обработку исключений, включая фильтрацию типов исключений. Порядок из обработчиков важно. Уменьшить методы небезопасных и rethrow переосмысливает последнее исключение в цепочке выполнения. Уменьшить методы иначе и 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);
    }
}

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

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 generics...), этот способ позволяет получить строгое статическое определение возможных исключений (требуется объявить их в throws). И нет!-Нужны -4--> или что-то.


Я согласен с комментариями выше, в использовании 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");

 }

}

единственный встроенный способ обработки проверенных исключений, которые могут быть вызваны map операция заключается в инкапсуляции их в CompletableFuture. (An 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 в my MonadUtils класс, который все еще находится в процессе работы.


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

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

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

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

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


вероятно, лучший и более функциональный способ-обернуть исключения и распространить их дальше в потоке. Взгляните на попробовать типа 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.


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

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

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

Я собираюсь выделить еще один вариант для этого, что ИМХО намного чище, чем все остальные: Ломбок @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 и без необходимости объявлять его явно.