Как я могу выбросить 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
в поток.
throws ClassNotFoundException
в методе, который содержит поток, чтобы вызывающий код ожидал и смог перехватить проверенное исключение.Ответы:
Простой ответ на ваш вопрос: вы не можете, по крайней мере, не напрямую. И это не твоя вина. Оракул все испортил. Они цепляются за концепцию проверенных исключений, но непонятно забыли позаботиться о проверенных исключениях при разработке функциональных интерфейсов, потоков, лямбды и т. Д. Все это относится к мельнице экспертов, таких как Роберт С. Мартин, которые называют проверенные исключения неудачным экспериментом.
На мой взгляд, это огромная ошибка в API и небольшая ошибка в спецификации языка .
Ошибка в API заключается в том, что он не предоставляет средств для пересылки проверенных исключений, где это на самом деле имеет большой смысл для функционального программирования. Как я покажу ниже, такая возможность была бы легко возможной.
Ошибка в спецификации языка заключается в том, что он не позволяет параметру типа выводить список типов вместо одного типа, если параметр типа используется только в ситуациях, когда список типов допустим (
throws
пункт).Как программисты на Java, мы ожидаем, что следующий код должен скомпилироваться:
Тем не менее, это дает:
Путь , в котором определены функциональные интерфейсы в настоящее время препятствует Compiler от пересылки исключения - нет декларации , которая бы сказать ,
Stream.map()
что еслиFunction.apply() throws E
,Stream.map() throws E
как хорошо.Чего не хватает, так это объявления параметра типа для прохождения через проверенные исключения. В следующем коде показано, как такой параметр сквозного типа действительно мог быть объявлен с текущим синтаксисом. За исключением особого случая в отмеченной строке, который является пределом, обсуждаемым ниже, этот код компилируется и ведет себя как ожидалось.
В случае, если
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 нет известных мне работ по смягчению взаимодействия между проверенными исключениями и лямбдами.
источник
try-catch
блок внутри лямбды, и это просто не имеет никакого смысла. Как толькоClass.forName
используется каким-либо образом в лямбда, например, вnames.forEach(Class::forName)
, проблема есть. По сути, методы, которые генерируют проверенные исключения, были исключены из участия в функциональном программировании как функциональные интерфейсы напрямую, из-за (плохого!) Дизайна.Этот
LambdaExceptionUtil
вспомогательный класс позволяет вам использовать любые проверенные исключения в потоках Java, например:Примечание
Class::forName
кидаетClassNotFoundException
, что проверено . Сам поток также выдаетClassNotFoundException
, а НЕ какое-то неконтролируемое исключение.Много других примеров того, как его использовать (после статического импорта
LambdaExceptionUtil
):Примечание 1: В
rethrow
методахLambdaExceptionUtil
указанного класса может быть использованы без страха, и OK для использования в любой ситуации . Большое спасибо пользователю @PaoloC, который помог решить последнюю проблему: теперь компилятор попросит вас добавить операторы throw и все так, как если бы вы могли генерировать отмеченные исключения в потоках Java 8.Примечание 2: В
uncheck
методахLambdaExceptionUtil
указанного класса являются методами бонуса, и может быть безопасно удалены их из класса , если вы не хотите использовать их. Если вы их использовали, делайте это с осторожностью, а не раньше, чем разбираетесь в следующих случаях использования, преимуществах / недостатках и ограничениях:• Вы можете использовать
uncheck
методы, если вы вызываете метод, который буквально никогда не может вызвать исключение, которое он объявляет. Например: new String (byteArr, "UTF-8") выбрасывает UnsupportedEncodingException, но UTF-8 гарантируется, что спецификация Java всегда присутствует. Здесь объявление throws является неприятностью, и любое решение заставить его замолчать с минимальными шаблонами приветствуется:String text = uncheck(() -> new String(byteArr, "UTF-8"));
• Вы можете использовать
uncheck
методы, если вы реализуете строгий интерфейс, в котором у вас нет возможности добавить объявление throws, и все же бросать исключение вполне уместно. Обтекание исключения только для того, чтобы получить привилегию выбросить его, приводит к трассировке стека с ложными исключениями, которые не дают никакой информации о том, что на самом деле пошло не так. Хорошим примером является Runnable.run (), который не выдает никаких проверенных исключений.• В любом случае, если вы решите использовать
uncheck
методы, имейте в виду следующие 2 последствия выброса исключений CHECKED без условия throws: 1) вызывающий код не сможет перехватить его по имени (если вы попытаетесь, компилятор скажет: исключение никогда не добавляется в тело соответствующего оператора try). Он будет пузыриться и, вероятно, попадет в основной цикл программы с помощью некоторого «catch Exception» или «catch Throwable», что в любом случае может быть тем, что вам нужно. 2) Это нарушает принцип наименьшего удивления: его уже не хватит, чтобы можно было отловитьRuntimeException
все возможные исключения. По этой причине я считаю, что это следует делать не в программном коде, а только в бизнес-коде, который вы полностью контролируете.источник
@SuppressWarnings ("unchecked")
хитрость компилятора совершенно неприемлема.Вы не можете сделать это безопасно. Вы можете обманывать, но тогда ваша программа будет нарушена, и это неизбежно вернется, чтобы укусить кого-то (это должен быть вы, но часто наш обман обрушивается на кого-то другого).
Вот немного более безопасный способ сделать это (но я все еще не рекомендую это.)
Здесь вы перехватываете исключение в лямбда-выражении, выбрасываете сигнал из потокового конвейера, который указывает на то, что вычисления не удалось, перехватывает сигнал, и действует на этот сигнал, чтобы вызвать основное исключение. Ключевым моментом является то, что вы всегда перехватываете синтетическое исключение, а не допускаете утечки проверенного исключения, не объявляя об этом исключении.
источник
Function
etc, ничего не делаютthrows
; Мне просто любопытно.throw w.cause;
не сделало бы компилятор жалуется , что метод не бросает и не пойматьThrowable
? Таким образом, вполне вероятно, что тамIOException
будет необходим актерский состав . Кроме того, если лямбда выдает более одного типа проверенных исключений, тело улова станет несколько уродливым с некоторымиinstanceof
проверками (или чем-то еще с аналогичной целью), чтобы проверить, какое проверенное исключение было выброшено.Ты можешь!
Расширяя @marcg
UtilException
и добавляя,throw E
где необходимо: таким образом, компилятор попросит вас добавить предложения throw и все так, как если бы вы могли изначально генерировать отмеченные исключения в потоках java 8.Инструкции: просто скопируйте / вставьте
LambdaExceptionUtil
в вашу IDE, а затем используйте его, как показано нижеLambdaExceptionUtilTest
.Некоторый тест, чтобы показать использование и поведение:
источник
<Integer>
раньшеmap
. Фактически, компилятор Java не может определитьInteger
тип возвращаемого значения. Все остальное должно быть правильно.Exception
.Просто используйте любой из NoException (мой проект), jOOλ's Unchecked , throwing- lambdas , Throwable интерфейсов или Faux Pas .
источник
Я написал библиотеку, которая расширяет Stream API, чтобы вы могли генерировать проверенные исключения. Он использует трюк Брайана Гетца.
Ваш код станет
источник
Этот ответ похож на 17, но избегая определения исключений оболочки:
источник
Ты не можешь.
Тем не менее, вы можете взглянуть на один из моих проектов, который позволяет вам легче манипулировать такими «бросающими лямбдами».
В вашем случае вы сможете сделать это:
и поймать
MyException
.Это один пример. Другим примером является то, что вы можете использовать
.orReturn()
значение по умолчанию.Обратите внимание, что это все еще в стадии разработки, еще не все. Лучшие имена, больше возможностей и т.д.
источник
.orThrowChecked()
метод в ваш проект, который позволяет выбрасывать само проверенное исключение , Пожалуйста, посмотрите на мойUtilException
ответ на этой странице и посмотрите, нравится ли вам идея добавить эту третью возможность в ваш проект.Stream
реализуетAutoCloseable
?MyException
должно ли ваше вышеупомянутое быть непроверенным исключением?Подводя итоги вышеприведенным комментариям, мы предлагаем использовать специальную оболочку для непроверенных функций с помощью компоновщика, такого как API, который обеспечивает восстановление, повторное использование и подавление.
Код ниже демонстрирует это для интерфейсов Consumer, Supplier и Function. Это может быть легко расширено. Некоторые публичные ключевые слова были удалены для этого примера.
Класс Try является конечной точкой для клиентского кода. Безопасные методы могут иметь уникальное имя для каждого типа функции. CheckedConsumer , CheckedSupplier и CheckedFunction являются проверенными аналогами функций lib, которые можно использовать независимо от Try
CheckedBuilder - это интерфейс для обработки исключений в некоторых проверенных функциях. orTry позволяет выполнить другую функцию того же типа, если предыдущая была неудачной. handle обеспечивает обработку исключений, включая фильтрацию типов исключений. Порядок обработчиков важен. Сокращение методов небезопасно и повторное отбрасывание последнего исключения в цепочке выполнения. Методы редукции orElse и orElseGet возвращают альтернативные значения, такие как Необязательные, если все функции не выполнены. Также есть метод подавления . CheckedWrapper является распространенной реализацией CheckedBuilder.
источник
TL; DR Просто используйте Ломбок
@SneakyThrows
.Кристиан Худжер уже подробно объяснил, почему выбрасывание проверенных исключений из потока, строго говоря, невозможно из-за ограничений Java.
В некоторых других ответах объясняются хитрости, позволяющие обойти ограничения языка, но при этом он способен выполнить требование «самого проверенного исключения и без добавления уродливых try / catch для потока» , некоторые из них требуют десятков дополнительных строк. шаблонной.
Я собираюсь выделить другой вариант для этого, который ИМХО намного чище, чем все остальные: Ломбок
@SneakyThrows
. Это было упомянуто мимоходом другими ответами, но было немного погребено под множеством ненужных подробностей.Полученный код так же прост, как:
Нам просто потребовался один
Extract Method
рефакторинг (выполненный IDE) и еще одна строка для@SneakyThrows
. Аннотация заботится о добавлении всего шаблона, чтобы убедиться, что вы можете выбросить проверенное исключение, не оборачивая егоRuntimeException
и не требуя явного его объявления.источник
Вы также можете написать метод-обертку, чтобы обернуть непроверенные исключения, и даже расширить обертку с помощью дополнительного параметра, представляющего другой функциональный интерфейс (с тем же типом возврата R ). В этом случае вы можете передать функцию, которая будет выполнена и возвращена в случае исключений. Смотрите пример ниже:
источник
Вот другой взгляд или решение исходной проблемы. Здесь я показываю, что у нас есть возможность написать код, который будет обрабатывать только допустимое подмножество значений с возможностью обнаруживать и обрабатывать случаи, когда было выдано исключение.
источник
Я согласен с комментариями выше, при использовании Stream.map вы ограничены реализацией функции, которая не генерирует исключения.
Однако вы можете создать свой собственный FunctionalInterface, который выдает, как показано ниже.
затем реализуйте его, используя Lambdas или ссылки, как показано ниже.
источник
Единственный встроенный способ обработки проверенных исключений, которые могут быть сгенерированы
map
операцией, - это инкапсулировать их вCompletableFuture
. (ЭтоOptional
более простая альтернатива, если вам не нужно сохранять исключение.) Эти классы предназначены для того, чтобы вы могли функционально представлять условные операции.Требуется пара нетривиальных вспомогательных методов, но вы можете прийти к сравнительно лаконичному коду, в то же время делая очевидным, что результат вашего потока зависит от
map
успешного завершения операции. Вот как это выглядит:Это производит следующий вывод:
applyOrDie
Метод принимает ,Function
что бросает исключение, и преобразует его вFunction
том , что возвращает уже завершеноCompletableFuture
- либо завершено , как правило , с результатом исходной функции, либо завершается исключительно с брошенным исключением.Вторая
map
операция показывает, что теперь у вас естьStream<CompletableFuture<T>>
вместо простоStream<T>
.CompletableFuture
выполняет только эту операцию, если вышестоящая операция выполнена успешно. API делает это объяснением, но относительно безболезненным.Пока вы не дойдете до
collect
фазы, то есть. Вот где нам требуется довольно значительный вспомогательный метод. Мы хотим , чтобы «поднять» нормальную работу коллекции (в данном случаеtoList()
) «внутри»CompletableFuture
-cfCollector()
позволяют нам сделать это с помощьюsupplier
,accumulator
,combiner
, иfinisher
что не нужно знать вообще ничего оCompletableFuture
.Вспомогательные методы можно найти на GitHub в моем
MonadUtils
классе, который все еще находится в стадии разработки.источник
Вероятно, лучший и более функциональный способ - обернуть исключения и распространять их дальше в потоке. Взгляните, например, на тип Try Vavr .
Пример:
ИЛИ
2-я реализация избегает включения исключения в
RuntimeException
.throwUnchecked
работает, потому что почти всегда все общие исключения рассматриваются как непроверенные в Java.источник
Я использую этот вид исключения упаковки:
Это потребует обработки этих исключений статически:
Попробуйте онлайн!
Хотя исключение в любом случае будет переброшено во время первого
rethrow()
вызова (о, Java-дженерики ...), этот способ позволяет получить строгое статическое определение возможных исключений (требуется их объявить вthrows
). И нетinstanceof
или что-то нужно.источник
Я думаю, что этот подход является правильным:
Оборачивание проверенного исключения внутри
Callable
inUndeclaredThrowableException
(это вариант использования для этого исключения) и развертывание его снаружи.Да, я нахожу это уродливым, и я бы посоветовал не использовать лямбды в этом случае и просто вернуться к старому доброму циклу, если только вы не работаете с параллельным потоком, и паралеллизация приносит объективное преимущество, которое оправдывает нечитаемость кода.
Как отмечали многие другие, в этой ситуации есть решения, и я надеюсь, что один из них превратится в будущую версию Java.
источник