it-swarm.com.ru

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

Как я могу выбросить 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 в поток.

246
MarcG

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

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

Примечание Class::forName выдает ClassNotFoundException, который является checked. Сам поток также генерирует 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) вызывающий код не сможет перехватить его по имени (если вы попытаетесь, компилятор скажет: исключение никогда не выдается в теле соответствующей попытки заявление). Он будет пузыриться и, вероятно, попадет в основной цикл программы с помощью некоторого «catch Exception» или «catch Throwable», что в любом случае может быть тем, что вам нужно. 2) Это нарушает принцип наименьшего удивления: вам больше не будет достаточно поймать RuntimeException, чтобы иметь возможность гарантировать перехват всех возможных исключений. По этой причине я считаю, что это следует делать не в программном коде, а только в бизнес-коде, который вы полностью контролируете.

158
MarcG

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

На самом деле это огромная ошибка в 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(), что если 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.

219
Christian Hujer

Вы можете!

Расширяя @marcg UtilException и добавляя throw E, где это необходимо: таким образом, компилятор попросит вас добавить предложения throw и все будет так, как если бы вы могли выбрасывать отмеченные исключения natively в потоки 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());
    }

}
21
PaoloC

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

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

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

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

21
Brian Goetz

Просто используйте любой из 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));
11
Robert Važan

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

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

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());
}
6
Jeffrey

Этот ответ похож на 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
        }
6
Radoslav Stoyanov

Ты не можешь.

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

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

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

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

5
fge

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

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

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

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

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

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

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) и дополнительная строка one для @SneakyThrows. Аннотация заботится о добавлении всего шаблона, чтобы вы могли выбросить проверенное исключение, не заключая его в RuntimeException и не требуя явного его объявления. 

1
sergut

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

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
Taras

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

OR

@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.

0
Mikhail Kholodkov

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

 }

}
0
JohnnyO

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

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

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

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

0
Paramaeleon

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

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;
}
0
Piotr Niewinski

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

0
Matt McHenry

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

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