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

Как я могу удалить CHECKED исключения изнутри 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.

Ответ 1

Этот вспомогательный класс 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, но в спецификации Java всегда присутствует UTF-8. Здесь объявление 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 чтобы иметь возможность гарантировать перехват всех возможных исключений. По этой причине я считаю, что это следует делать не в программном коде, а только в бизнес-коде, который вы полностью контролируете.

Ответ 2

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

На мой взгляд, это огромная ошибка в 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);
    }
}

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

[email protected]:~/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() бы 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 проверенных исключений ссылочного метода, а не одного супертипа типа catch-all.

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

Ответ 3

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

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

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

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

Ответ 4

Вы можете!

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

}

Ответ 5

Просто используйте любой из NoException (мой проект), jOOX Unchecked, throwing-lambdas, Throwable interfaces, или 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));

Ответ 6

Я написал библиотеку, которая расширяет 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
        }

Ответ 8

Вы не можете.

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

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

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

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

Ответ 9

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

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

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

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

Ответ 10

TL; DR Просто используйте Lombok @SneakyThrows.

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

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

Я собираюсь выделить другой вариант для этого, который IMHO намного чище, чем все остальные: Lombok @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 его в RuntimeException и не требуя его явного объявления.

Ответ 11

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

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

@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");

 }

}

Ответ 12

Единственный встроенный способ обработки проверенных исключений, которые может быть вызван операцией 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 в классе MonadUtils, который очень много работает.

Ответ 13

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

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). И никакой instanceof или что-то не нужно.

Ответ 14

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

Ответ 15

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

Ответ 16

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

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

Ответ 17

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

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 проверенного исключения внутри Callable в UndeclaredThrowableException (это вариант использования для этого исключения) и развертывание его снаружи.

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

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