Как я могу бросить проверенные исключения из потоков 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
наClassNotFoundException
иIOException
, которая составляетException
.требуется настройка определения вывода типа, чтобы компилятор искал несколько типов, если параметр типа используется там, где допустим список типов (
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: The
rethrow
методыLambdaExceptionUtil
классом выше может быть используется без страха, и являются ОК использовать в любой ситуации. Большое спасибо пользователю @PaoloC, который помог решить последнюю проблему: теперь компилятор попросит вас добавить предложения throw, и все так, как если бы вы могли бросить проверенные исключения изначально на потоках Java 8.
примечание 2: The
uncheck
методыLambdaExceptionUtil
класс выше-это бонусные методы, и их можно безопасно удалить из класса, если вы не хотите их использовать. Если вы использовали их, сделайте это с осторожностью, а не до понимания следующих случаев использования, преимуществ / недостатков и ограничений:• вы можете использовать
uncheck
методы Если вы вызываете метод, который буквально не может бросить исключение, которое его заявляет. Например: новая строка (byteArr, "UTF-8") бросает UnsupportedEncodingException, но UTF-8 гарантируется спецификацией Java, чтобы всегда присутствовать. Здесь объявление бросков является неприятностью, и любое решение заставить его замолчать с минимальным шаблоном приветствуется:String text = uncheck(() -> new String(byteArr, "UTF-8"));
• вы можете использовать
uncheck
методы Если вы реализуете строгий интерфейс, где у вас нет возможности добавить объявление бросков, и все же бросание исключения полностью уместно. Обертывание исключения только для того, чтобы получить привилегию бросать его, приводит к stacktrace с ложными исключениями, которые не вносят никакой информации о том, что на самом деле пошло не так. Хорошим примером является запускаемым.run (), который не бросает никаких проверенных исключения.• в любом случае, если вы решили использовать
uncheck
методы, имейте в виду эти 2 последствия выбрасывания проверенных исключений без предложения throws: 1) вызывающий код не сможет поймать его по имени (если вы попытаетесь, компилятор скажет: исключение никогда не выбрасывается в теле соответствующего оператора try). Он будет пузыриться и, вероятно, будет пойман в основном цикле программы каким-то "catch Exception" или "catch Throwable", что может быть тем, что вы хотите в любом случае. 2) он нарушает принцип наименьшего удивления: его уже будет недостаточно пойматьRuntimeException
чтобы иметь возможность гарантировать отлов всех возможных исключений. По этой причине я считаю, что это не должно быть сделано в коде framework, а только в бизнес-коде, который вы полностью управление.
- ссылки:
- http://www.philandstuff.com/2012/04/28/sneakily-throwing-checked-exceptions.html
- http://www.mail-archive.com/javaposse@googlegroups.com/msg05984.html
- проект Ломбок аннотация: @SneakyThrows
- мнение Брайана Гетца (против) здесь:как я могу бросить проверенные исключения из Java 8 ручьи?
- https://softwareengineering.stackexchange.com/questions/225931/workaround-for-java-checked-exceptions?newreg=ddf0dd15e8174af8ba52e091cf85688e *
вы не можете сделать это безопасно. Вы можете обмануть, но тогда ваша программа сломается, и это неизбежно вернется, чтобы укусить кого-то (это должен быть вы, но часто наш обман взрывается на кого-то другого.)
вот немного более безопасный способ сделать это (но я все еще не рекомендую это.)
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()
некоторое значение по умолчанию.обратите внимание, что это все еще работа в процессе, еще впереди. Лучшие имена, больше возможностей так далее.
обобщение комментариев выше передовое решение заключается в использовании специальной оболочки для непроверенных функций с помощью builder, такого как 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 возвращает последнее исключение в цепочке исполнения. Уменьшить методы 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); } }
я использую этот вид исключения упаковки:
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
). И нет или что-то.
Я согласен с комментариями выше, в использовании потока.карта вы ограничены реализацией функции, которая не вызывает исключений.
однако вы можете создать свой собственный функциональный интерфейс, который бросает, как показано ниже..
@FunctionalInterface public interface UseInstance<T, X extends Throwable> { void accept(T instance) throws X; }
затем реализуйте его с помощью лямбд или ссылок, как показано ниже.
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
. (Ан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]
The
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
(Это вариант использования для этого исключения) и разворачивание его снаружи.Да, я нахожу это уродливым, и я бы посоветовал не использовать лямбды в этом случае и просто вернуться к старой доброй петле, если вы не работаете с параллельным потоком, а паралеллизация приносит объективную выгоду, которая оправдывает нечитаемость код.
как указывали многие другие, есть решения для этой ситуации, и я надеюсь, что один из них сделает это в будущей версии 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
и без необходимости объявлять его явно.