Як я можу викидати ПРОВЕРЕНІ винятки з потоків Java 8?


287

Як я можу викидати ПРОВЕРЕНІ винятки з потоків / лямбдав 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у потік.


42
Коротку відповідь ви не можете, не порушуючи правил щодо винятків. Ви, звичайно, можете обдурити, і інші з радістю покажуть вам, як, але будьте в курсі, що це обман і таке обман часто повертається, щоб вас вкусити. Ви повинні зловити виняток і боротися з ним. Якщо ви хочете обернути його, а потім повторно скинути перевірений виняток після, ви можете це зробити безпечно.
Брайан Гец

35
@Brian, мені не потрібні інші, щоб мені говорили, як обманювати, я знаю, як обдурити себе, і розмістив свій спосіб обману у відповіді нижче, яку ви обрали. Я знаю, що ви берете участь у дискусії на Java, яка вирішила, що не існує хорошого способу боротьби з перевіреними винятками в Streams, тому мені здається дивним, що ви помітили це моє питання, але я розчарований вашою відповіддю, яка просто говорить "це не добре ", не дає жодних причин, а потім додає спробу / ловить все заново.
MarcG

22
@Brian, чесно кажучи, на практиці, коли люди намагаються відтворити спадщину для висловлювань, половина з них перетворюється на потоки, а інша половина відмовляється від рефакторингу, тому що ніхто не хоче додавати ці спроби / улови. Вони надають код набагато важче для читання, безумовно, більше, ніж оригінальні для-заяви. У моєму прикладі коду вище, поки ви підтримуєте "кидає ClassNotFoundException", я не бачу різниці із зовнішнім кодом. Чи можете ви, будь ласка, надати мені кілька прикладів із реального життя, коли це порушує правила щодо винятків?
MarcG

10
Написання методів обгортки, які переносяться на неперевірені винятки, вирішує заперечення "безладу коду" і не порушує систему типів. Тут відповідь, що вдається до "підлий кидок" перевіреного винятку, порушує систему типу, оскільки викликовий код не очікує (і не дозволить зловити) перевірений виняток.
Брайан Гец

14
Він не вирішує заперечення проти коду, оскільки тоді вам потрібно повторити спробу / ловити навколо потоку, щоб розмотати та повторно скинути початкове виключення. Навпаки, якщо ви кидаєте перевірений виняток, вам просто потрібно зберегти throws ClassNotFoundExceptionв декларації методу, що містить потік, щоб код виклику очікував і дозволив зловити перевірений виняток.
MarcG

Відповіді:


250

Проста відповідь на ваше запитання: Ви не можете, принаймні, безпосередньо. І це не ваша вина. 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);
    }
}

Однак це дає:

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також.

Не вистачає декларації параметра типу для проходження перевірених винятків. Наступний код показує, як такий параметр типу "прохідний" насправді міг бути оголошений у поточному синтаксисі. За винятком спеціального випадку у позначеному рядку, який є обмеженням, про який йде мова нижче, цей код складається і поводиться так, як очікувалося.

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 заплутав це. Звичайно, вони не порушують код user-land, але введення параметрів типу виключення до існуючих функціональних інтерфейсів порушить компіляцію всього коду-користувача, який використовує ці інтерфейси явно. Їм доведеться винайти новий синтаксичний цукор, щоб виправити це.

Ще гірша новина полягає в тому, що цю тему вже обговорював Брайан Гец в 2010 році https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java (нове посилання: http://mail.openjdk.java.net/pipermail/lambda -dev / 2010-June / 001484.html ), але мені відомо, що це розслідування врешті-решт не вийшло з ладу і що в Oracle не існує поточної роботи, яка могла б пом'якшити взаємодію між перевіреними винятками та лямбдами.


16
Цікаво. Я вважаю, що деякі люди цінують потоки за те, що дозволяють простіший паралельний код, а інші - за більш чистий код. Брайан Гец, очевидно, більше піклується про паралелізм (оскільки він є автором Java Concurrency в практиці), а Роберт Мартін більше піклується про чистий код (оскільки він є автором книги «Чистий код»). Спроба / улов котлоагрегату - це незначна ціна, яку потрібно платити за паралелізм, тому недарма Брайана Геца не вражають проблеми використання перевірених винятків у потоках. Також недарма Роберт Мартін ненавидить перевіряти винятки, оскільки вони додають клопоту.
MarcG

5
Я прогнозую, що через кілька років складність роботи з перевіреними винятками всередині потоків призведе до одного з цих двох результатів: Люди просто перестануть використовувати перевірені винятки, АБО всі почнуть використовувати якийсь хак дуже подібно до того, який я розмістив у моя відповідь UtilException. Я б сказав, що потоки Java-8 - це останній цвях у труні перевірених винятків, як не факт, що перевірені винятки є частиною JDK. Хоча я люблю і використовую перевірені винятки в бізнес-коді (для деяких конкретних випадків використання), я б віддав перевагу всім звичайним виняткам JDK, розширеним Runtime.
MarcG

9
@Unihedro Проблема залишається в тому, що функціональні інтерфейси не передають винятків. Мені знадобиться try-catchблок всередині лямбда, і це просто не має сенсу. Як тільки Class.forNameякимось чином використовується в лямбда, наприклад, в names.forEach(Class::forName), проблема є. В основному, методи, які викидають перевірені винятки, були виключені з участі у функціональному програмуванні як функціональних інтерфейсах безпосередньо, (погана!) Конструкція.
Крістіан Худжер

26
@ChristianHujer Дослідження "прозорості винятку" було саме цим - розвідкою (такою, що виникла в пропозиції BGGA). Після більш глибокого аналізу ми виявили, що він пропонує поганий співвідношення вартості та складності, і у нього виникли серйозні проблеми (призвели до невирішених проблем з висновками, і "catch X" було невідомим, серед інших.) Надзвичайно часто зустрічається мовна ідея здається багатообіцяючим - навіть "очевидним" - але після більш глибокого дослідження виявилося недоліком. Це був один із таких випадків.
Брайан Гец

13
@BrianGoetz Чи є якась публічна інформація щодо нерозв'язних проблем з висновками, про які ви згадали? Мені цікаво і хотілося б це зрозуміти.
Крістіан Худжер

169

Цей LambdaExceptionUtilдопоміжний клас дозволяє використовувати будь-які перевірені винятки в потоках Java, наприклад:

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

Зверніть увагу на Class::forNameкидки ClassNotFoundException, які перевіряються . Сам потік також викидає ClassNotFoundException, а НЕ якийсь обертовий неперевірений виняток.

public final class LambdaExceptionUtil {

@FunctionalInterface
public interface Consumer_WithExceptions<T, E extends Exception> {
    void accept(T t) throws E;
    }

@FunctionalInterface
public interface BiConsumer_WithExceptions<T, U, E extends Exception> {
    void accept(T t, U u) throws E;
    }

@FunctionalInterface
public interface Function_WithExceptions<T, R, E extends Exception> {
    R apply(T t) throws E;
    }

@FunctionalInterface
public interface Supplier_WithExceptions<T, E extends Exception> {
    T get() throws E;
    }

@FunctionalInterface
public interface Runnable_WithExceptions<E extends Exception> {
    void run() throws E;
    }

/** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */
public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
    return t -> {
        try { consumer.accept(t); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> biConsumer) throws E {
    return (t, u) -> {
        try { biConsumer.accept(t, u); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

/** .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName)) */
public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E {
    return t -> {
        try { return function.apply(t); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))), */
public static <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> function) throws E {
    return () -> {
        try { return function.get(); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** uncheck(() -> Class.forName("xxx")); */
public static void uncheck(Runnable_WithExceptions t)
    {
    try { t.run(); }
    catch (Exception exception) { throwAsUnchecked(exception); }
    }

/** uncheck(() -> Class.forName("xxx")); */
public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier)
    {
    try { return supplier.get(); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

/** uncheck(Class::forName, "xxx"); */
public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) {
    try { return function.apply(t); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

@SuppressWarnings ("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; }

}

Багато інших прикладів його використання (після статичного імпорту LambdaExceptionUtil):

@Test
public void test_Consumer_with_checked_exceptions() throws IllegalAccessException {
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(className -> System.out.println(Class.forName(className))));

    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(System.out::println));
    }

@Test
public void test_Function_with_checked_exceptions() throws ClassNotFoundException {
    List<Class> classes1
          = Stream.of("Object", "Integer", "String")
                  .map(rethrowFunction(className -> Class.forName("java.lang." + className)))
                  .collect(Collectors.toList());

    List<Class> classes2
          = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                  .map(rethrowFunction(Class::forName))
                  .collect(Collectors.toList());
    }

@Test
public void test_Supplier_with_checked_exceptions() throws ClassNotFoundException {
    Collector.of(
          rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))),
          StringJoiner::add, StringJoiner::merge, StringJoiner::toString);
    }

@Test    
public void test_uncheck_exception_thrown_by_method() {
    Class clazz1 = uncheck(() -> Class.forName("java.lang.String"));

    Class clazz2 = uncheck(Class::forName, "java.lang.String");
    }

@Test (expected = ClassNotFoundException.class)
public void test_if_correct_exception_is_still_thrown_by_method() {
    Class clazz3 = uncheck(Class::forName, "INVALID");
    }    

Примітка 1: У rethrowметодах LambdaExceptionUtilзазначеного класу може бути використані без страху, і OK для використання в будь-якій ситуації . Велика подяка користувачеві @PaoloC, який допоміг вирішити останню проблему: тепер компілятор попросить вас додати пункти закидання, і все так, ніби ви можете кинути перевірені винятки спочатку в потоках Java 8.


Примітка 2: У uncheckспособах поLambdaExceptionUtil зазначеного класу є методами бонусу, і може бути безпечно видалені їх з класу , якщо ви не хочете використовувати їх. Якщо ви все-таки використовували їх, робіть це обережно, не розуміючи наступних випадків використання, переваг / недоліків та обмежень:

• Ви можете використовувати uncheckметоди, якщо ви викликаєте метод, який буквально ніколи не може кинути виняток, який він оголошує. Наприклад: нова String (byteArr, "UTF-8") кидає UnsupportedEncodingException, але UTF-8 гарантує, що специфікація Java завжди буде присутня. Тут декларація кидків викликає неприємність, і будь-яке рішення, щоб замовкнути її мінімальними плитами, вітається:String text = uncheck(() -> new String(byteArr, "UTF-8"));

• Ви можете використовувати uncheckметоди, якщо ви реалізуєте суворий інтерфейс, коли у вас немає можливості додавати декларацію про кидки, і все ж кидати виняток цілком доречно. Якщо вимкнути виняток лише для того, щоб отримати привілей на його викид, це призведе до стек-тракції з хибними винятками, які не дають інформації про те, що насправді пішло не так. Хороший приклад - Runnable.run (), який не кидає перевірених винятків.

• У будь-якому випадку, якщо ви вирішили скористатися uncheckметодами, пам’ятайте про ці 2 наслідки відкидання ПРОВЕРЕНОГО винятків без пункту закидання: 1) Call-код не зможе впізнати його по імені (якщо ви спробуєте, компілятор скаже: Виняток ніколи не викидається в тіло відповідного спробуйте. Він буде бульбашком і, ймовірно, потрапить у основний цикл програми деяким "Виключенням вилову" або "Ловом Throwable", що може бути те, що ви хочете в будь-якому випадку. 2) Це порушує принцип найменшого сюрпризу: його більше не буде достатньо для вилову, RuntimeExceptionщоб мати можливість гарантувати вилов усіх можливих винятків. З цієї причини я вважаю, що це слід робити не в рамковому коді, а лише в бізнес-коді, який ви повністю контролюєте.


4
Я вважаю, що ця відповідь була несправедливою. Код працює. Перевірені винятки повинні бути кинуті або вирішені. Якщо ви хочете їх викинути, просто дотримуйтесь пункту "кидки" у методі, який містить потік. Але якщо ви хочете розібратися з ними, просто загорнувшись і повторно перекинувшись, я вважаю, що я вважаю за краще використовувати вищевказаний код, щоб "зняти" винятки і дозволити їм міхувати самостійно. Я знаю єдину різницю в тому, що винятковий виняток не поширюватиме RuntimeException. Я знаю, що пуристам це не сподобається, але це "неминуче повернеться, щоб когось вкусити"? Це не здається ймовірним.
MarcG

4
@ Chhristian Hujer, якщо чесно сказати з downvoter, він відмовився від попередньої версії, перш ніж я додав пояснення "переваги, недоліки та обмеження". То, можливо, на той час це заслужили. Ви не можете навчити когось порушувати правила, хоча б не намагаючись зрозуміти та пояснити наслідки. Основною причиною, чому я опублікував це питання, було отримання зворотного зв’язку щодо недоліків моєї відповіді. Я отримав цей зворотній зв'язок не тут, а з іншого питання програмістів.stackexchange. Потім я повернувся сюди і оновив свою відповідь.
MarcG

16
Я прихильнився лише тому, що це заохочує нездійсненний код . Це некрасивий хакер, хоч і спритний, і я ніколи не вважаю цю відповідь корисною. Це, знову ж таки, ще одне "не вживання" мови.
Unihedron

12
@Unihedro, але чому це стало неможливим? Я не можу зрозуміти, чому. Будь-які приклади?
MarcG

2
На мою думку, @SuppressWarnings ("unchecked")хитрість компілятора абсолютно неприйнятна.
Thorbjørn Ravn Andersen

26

Ви не можете зробити це безпечно. Можна обдурити, але тоді ваша програма порушена, і це неминуче повернеться, щоб вкусити когось (це повинні бути ви, але часто наше обманюється на когось іншого.)

Ось дещо безпечніший спосіб зробити це (але я все одно не рекомендую цього.)

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

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


18
Лише питання; яке проектне рішення призвело до того, що лямбдам не вдалося поширювати перевірені винятки поза їх контекстом? Зауважте, що я розумію, що функціональні інтерфейси, такі як Functionтощо, нічого не роблять throws; Мені просто цікаво.
fge

4
Це throw w.cause;не змусило б компілятор скаржитися, що метод не кидає і не ловить Throwable? Тож, цілком ймовірно, що IOExceptionтам потрібен буде акторський склад. Крім того, якщо лямбда кидає більше одного типу перевірених винятків, тіло улову стане трохи негарним з деякими instanceofчеками (або чимось іншим із подібною метою), щоб перевірити, який перевірений виняток був кинутий.
Віктор Стафуса

10
@schatten Однією з причин є те, що ви можете забути зловити МЕ, і тоді дивний виняток (з яким ніхто не знає, як з цим боротися) витік. (Ви можете сказати, "але ви вилучили виняток, тому його безпечно". У цьому прикладі іграшки. Але кожного разу, коли я бачив, як база даних коду застосовує такий підхід, з часом хтось забуває. Спокусі ігнорувати винятки не існує меж.) Ще один ризик полягає в тому, що безпечне його використання є специфічним для певної комбінації (використовувати сайт, виняток). Він не відповідає масштабам численних винятків або однодоменного використання.
Брайан Гец

2
@hoodaticus Я згоден з вами. Ви сказали, що ви віддаєте перевагу обгортанню все більше і більше (як показано вище, збільшуючи ризик "забути") або просто створити 4 розумні інтерфейси та використовувати лямбдаш без обертання, як показано в stackoverflow.com/a/30974991/2365724 ? Спасибі
PaoloC

10
Відверто кажучи, це рішення просто абсолютно нездійсненне. Я думав, що суть потоків у тому, щоб зменшити котельну плиту, а не збільшувати її.
wvdz

24

Ти можеш!

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

}

1
Вибачте @setheron ви праві, просто додайте <Integer>раніше map. Насправді компілятор Java не може зробити висновок про Integerтип повернення. Все інше повинно бути правильним.
PaoloC

1
Це працювало для мене. Це зробило відповідь MarcG досконалою, застосувавши поводження з винятком.
Скічан

1
Вирішення вищезазначеного питання: оголосити змінну на зразок цього Consumer <ThingType> виразом = rethrowConsumer ((ThingType річ) -> thing.clone ()); потім використовуйте це вираження всередині внутрішнього передбачення.
Skychan

1
@Skychan: Оскільки у цій модифікованій новій версії ви більше не пригнічуєте жодних винятків, це, мабуть, трохи складніше для системи висновку. У коментарі нижче Брайан Гец розповідає про "прозорість винятку", що призводить до "нерозв'язних проблем з висновками".
MarcG

3
Дуже хороша. Єдине прикро, що це не працює ідеально з методом, який кидає кілька перевірених винятків. У цьому випадку компілятор змусить вас зафіксувати загальний супертип, наприклад Exception.
wvdz

12

Просто використовуйте будь-який з NoException (мій проект), jOOλ "Не перевірено" , метання лямбда , інтерфейси , що перекидаються , або 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));

7

Я написав бібліотеку, яка розширює 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());
}

7

Ця відповідь схожа на 17, але уникаючи визначення виключення для обгортки:

List test = new ArrayList();
        try {
            test.forEach(obj -> {

                //let say some functionality throws an exception
                try {
                    throw new IOException("test");
                }
                catch(Exception e) {
                    throw new RuntimeException(e);
                }
            });
        }
        catch (RuntimeException re) {
            if(re.getCause() instanceof IOException) {
                //do your logic for catching checked
            }
            else 
                throw re; // it might be that there is real runtime exception
        }

1
Це просте і ефективне рішення.
Лін Ш

2
Саме цього Оп не хотів: спробуйте блоки в лямбда. Крім того, він працює лише так, як очікувалося, поки жоден інший код поза блоком спробу не заверне IOException в RuntimeException. Щоб уникнути цього, може бути використана спеціальна обгортка - RuntimeException (визначена як приватний внутрішній клас).
Мальте Хартвіг

5

Ви не можете.

Однак ви можете поглянути на один із моїх проектів, який дозволяє вам легше маніпулювати такими "киданнями лямбда".

У вашому випадку ви зможете це зробити:

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()якесь значення за замовчуванням.

Зауважте, що ЦІЛЬ це незавершена робота, ще чекає ще. Кращі назви, більше функцій тощо.


2
Але тоді, якщо ви хочете викинути оригінальний перевірений виняток, вам доведеться додати спробувати / ловити навколо потоку, щоб розмотати його, що все-таки страшно! Мені подобається думка про те, що Ви можете МОЖЕТ викинути неперевірений виняток, якщо хочете, і що Ви можете МОЖЕТ повернути значення потоку за замовчуванням у потік, але я також думаю, що Ви повинні додати .orThrowChecked()до свого проекту якийсь метод, який дозволяє скинути перевірене виключення . Будь ласка, подивіться на мою UtilExceptionвідповідь на цій сторінці та побачте, чи подобається вам ідея додати цю третю можливість до свого проекту.
MarcG

"Але тоді, якщо ви хочете викинути оригінальний перевірений виняток, вам доведеться додати спробувати / ловити навколо потоку, щоб розмотати його, що все ще страшно!" <- так, але у вас немає вибору. Лямбда не може поширювати перевірені винятки поза їх контекстом, це дизайнерське "рішення" (я розглядаю це як недолік, особисто, але оуелл)
fge

Щодо вашої ідеї, я не дуже дотримуюся того, що вона робить, вибачте; зрештою, ти все ще кидаєш як неперевірений, так чим це відрізняється від того, що я роблю? (за винятком того, що у мене інший інтерфейс для нього)
fge

У будь-якому випадку, ви можете зробити свій внесок у проект! Також ви помітили, що Streamзнаряддя AutoCloseable?
fge

Дозвольте запитати вас у цьому: чи MyExceptionпотрібно, щоб ваше вище було неперевіреним винятком?
MarcG

3

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

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

TL; DR Просто використовуйте Lombok @SneakyThrows .

Крістіан Худжер уже детально пояснив, чому викидати перевірені винятки з потоку, строго кажучи, неможливо через обмеження Java.

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

Я збираюся виділити ще один варіант для цього: ІМХО набагато чистіший за всі інші: Ломбок @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та без необхідності чітко заявляти про це.


4
Вживання ломбоку слід не рекомендувати.
Драгас

2

Ви також можете написати метод обгортки, щоб обернути неперевірені винятки, і навіть покращити обгортку додатковим параметром, що представляє інший функціональний інтерфейс (з тим же типом повернення R ). У цьому випадку ви можете передавати функцію, яка буде виконуватися і повертатися у випадку винятків. Дивіться приклад нижче:

private void run() {
    List<String> list = Stream.of(1, 2, 3, 4).map(wrapper(i ->
            String.valueOf(++i / 0), i -> String.valueOf(++i))).collect(Collectors.toList());
    System.out.println(list.toString());
}

private <T, R, E extends Exception> Function<T, R> wrapper(ThrowingFunction<T, R, E> function, 
Function<T, R> onException) {
    return i -> {
        try {
            return function.apply(i);
        } catch (ArithmeticException e) {
            System.out.println("Exception: " + i);
            return onException.apply(i);
        } catch (Exception e) {
            System.out.println("Other: " + i);
            return onException.apply(i);
        }
    };
}

@FunctionalInterface
interface ThrowingFunction<T, R, E extends Exception> {
    R apply(T t) throws E;
}

2

Ось інший погляд або рішення вихідної проблеми. Тут я показую, що у нас є можливість написати код, який буде обробляти лише дійсний підмножина значень з можливістю виявлення та обробки випадків, коли викид був викинутий.

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

1

Я погоджуюся з вищезазначеними коментарями, використовуючи Stream.map, ви обмежуєтесь реалізацією Функції, яка не кидає винятків.

Однак ви можете створити власний функціональний інтерфейс, який відображається нижче.

@FunctionalInterface
public interface UseInstance<T, X extends Throwable> {
  void accept(T instance) throws X;
}

потім реалізуйте його, використовуючи Lambdas або посилання, як показано нижче.

import java.io.FileWriter;
import java.io.IOException;

//lambda expressions and the execute around method (EAM) pattern to
//manage resources

public class FileWriterEAM  {
  private final FileWriter writer;

  private FileWriterEAM(final String fileName) throws IOException {
    writer = new FileWriter(fileName);
  }
  private void close() throws IOException {
    System.out.println("close called automatically...");
    writer.close();
  }
  public void writeStuff(final String message) throws IOException {
    writer.write(message);
  }
  //...

  public static void use(final String fileName, final UseInstance<FileWriterEAM, IOException> block) throws IOException {

    final FileWriterEAM writerEAM = new FileWriterEAM(fileName);    
    try {
      block.accept(writerEAM);
    } finally {
      writerEAM.close();
    }
  }

  public static void main(final String[] args) throws IOException {

    FileWriterEAM.use("eam.txt", writerEAM -> writerEAM.writeStuff("sweet"));

    FileWriterEAM.use("eam2.txt", writerEAM -> {
        writerEAM.writeStuff("how");
        writerEAM.writeStuff("sweet");      
      });

    FileWriterEAM.use("eam3.txt", FileWriterEAM::writeIt);     

  }


 void writeIt() throws IOException{
     this.writeStuff("How ");
     this.writeStuff("sweet ");
     this.writeStuff("it is");

 }

}

1

Єдиний вбудований спосіб обробки перевірених винятків, які можуть бути викинуті mapоперацією, - це інкапсуляція їх у межах CompletableFuture. (AnOptional простіша альтернатива, якщо вам не потрібно зберігати виняток.) ​​Ці класи призначені для того, щоб представити умовні операції у функціональний спосіб.

Потрібно кілька нетривіальних допоміжних методів, але ви можете прийти до коду, який є досить стислим, але все одно даючи зрозуміти, що результат вашого потоку залежить від 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>>замість цього просто a Stream<T>. CompletableFutureдбає про виконання цієї операції лише в тому випадку, якщо операція вище за течією вдалася. API робить цей приклад, але відносно безболісним.

Поки ви не перейдете до collectфази, тобто. Саме тут нам потрібен досить значний помічний метод. Ми хочемо , щоб «підняти» нормальну роботу колекції (в даному випадку toList()) «всередині» CompletableFuture- cfCollector()дозволяють нам зробити це з допомогою supplier, accumulator, combiner, і finisherщо не потрібно знати взагалі нічого про CompletableFuture.

Допоміжні методи можна знайти на GitHub у моєму MonadUtilsкласі, який ще дуже працює.


1

Напевно, кращим і функціональнішим способом є загортання винятків і поширення їх далі в потоці. Погляньте, наприклад, на спробу типу 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);
        }
    };
}

Друга реалізація дозволяє уникнути загортання виключення в a RuntimeException. throwUncheckedпрацює, тому що майже завжди всі загальні винятки трактуються як перевірені в Java.


1

Я використовую такий виняток обгортки:

public class CheckedExceptionWrapper extends RuntimeException {
    ...
    public <T extends Exception> CheckedExceptionWrapper rethrow() throws T {
        throw (T) getCause();
    }
}

Буде потрібно статично обробляти ці винятки:

void method() throws IOException, ServletException {
    try { 
        list.stream().forEach(object -> {
            ...
            throw new CheckedExceptionWrapper(e);
            ...            
        });
    } catch (CheckedExceptionWrapper e){
        e.<IOException>rethrow();
        e.<ServletExcepion>rethrow();
    }
}

Спробуйте в Інтернеті!

Хоча виняток буде все-таки повторно викинуто під час першого rethrow()дзвінка (о, Java generics ...), цей спосіб дозволяє отримати суворе статичне визначення можливих винятків (вимагає оголосити їх у throws). І не instanceofпотрібно нічого або чогось.


-1

Я думаю, що такий підхід є правильним:

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в aUndeclaredThrowableException (у цьому випадку використовується цей виняток) та розгортання його зовні.

Так, я вважаю це некрасивим, і я б радив не використовувати лямбда в цьому випадку і просто повернутися до старого хорошого циклу, якщо ви не працюєте з паралельним потоком і паралелізація не приносить об'єктивної вигоди, яка виправдовує нечитабельність коду.

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


1
(1) Вже є кілька відповідей, що показують подібний приклад, так що ваша відповідь додає до запитань, які вже не охоплені? Опублікування дублікатів відповідей, як це, просто додає захаращення сайту. (2) ОП конкретно каже, що вони не хочуть цього робити. "Зверніть увагу, я НЕ хочу вказувати перевірений виняток всередині виключення під час виконання програми і замість цього викидати обгорнутий неперевірений виняток."
Radiodef
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.