Java 8: Як я можу працювати з методами метання винятків у потоках?


172

Припустимо, у мене є клас і метод

class A {
  void foo() throws Exception() {
    ...
  }
}

Тепер я хотів би зателефонувати foo для кожного екземпляра, який Aпередається потоком, наприклад:

void bar() throws Exception {
  Stream<A> as = ...
  as.forEach(a -> a.foo());
}

Питання: Як я правильно поводжусь із винятком? Код не збирається на моїй машині, оскільки я не обробляю можливі винятки, які можуть бути викинуті foo (). throws ExceptionЗ , barздається, марно тут. Чому так?



Відповіді:


141

Вам потрібно загорнути виклик методу в інший, де ви не кидаєте перевірені винятки . Ви все одно можете кинути все, що є підкласом RuntimeException.

Звичайна ідіома, що обгортає щось таке:

private void safeFoo(final A a) {
    try {
        a.foo();
    } catch (Exception ex) {
        throw new RuntimeException(ex);
    }
}

(Supertype винятком Exceptionє лише використовується в якості прикладу, не намагатися спіймати його самостійно)

Тоді ви можете назвати його: as.forEach(this::safeFoo).


1
Якщо ви хочете, щоб це був метод обгортки, я б оголосив його статичним. Він не використовує нічого з "цього".
aalku

206
Сумно, що ми маємо це робити замість того, щоб використовувати наші рідні винятки .... о Java, це дає, а потім забирає
Еріх,

1
@Stephan Цю відповідь було видалено, але вона все ще доступна тут: stackoverflow.com/a/27661562/309308
Michael Mrozek

3
Це миттєво не зможе переглянути перевірку коду в моїй компанії: нам не дозволяється кидати неперевірені винятки.
Stelios Adamantidis

7
@SteliosAdamantidis це правило неймовірно обмежувальне та протилежне продуктивності. чи знаєте ви, що всі методи в усіх api мають право викидати всі смаки винятків під час виконання, без того, щоб ви навіть знали про це (звичайно, ви)? Ви заборонили JavaScript за те, що не було реалізовано концепцію перевірених винятків? Якби я був вашим головним розробником, я б заборонив перевіряти винятки.
spi

35

Якщо все, що вам потрібно, - це посилатися foo, і ви віддаєте перевагу розповсюдженню виключення як є (без обгортання), ви можете також просто forзамість цього використовувати цикл Java (після перетворення потоку в Iterable з деякою хитрістю ):

for (A a : (Iterable<A>) as::iterator) {
   a.foo();
}

Це, принаймні, те, що я роблю в своїх тестах JUnit, де я не хочу переживати проблеми, пов'язані з перевіркою своїх перевірених винятків (і насправді віддаю перевагу моїм тестам викидати незагорнуті оригінали)


16

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

На мою думку, найелегантніший спосіб, який ви можете знайти, був наданий Мішею. Тут викладені винятки у виконанні потоків Java 8 , просто виконуючи дії у "ф'ючерсах". Таким чином, ви можете запускати всі робочі частини та збирати непрацюючі винятки як єдину. В іншому випадку ви можете зібрати їх у списку та обробити їх пізніше.

Аналогічний підхід походить від Бенджі Вебера . Він пропонує створити власний тип для збору робочих, а не працюючих деталей.

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

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


3
Це слід позначити як правильну відповідь. +. Але я не можу сказати, що це хороший пост. Ви повинні це детально розробити. Як міг би допомогти власний виняток? Які трапляються винятки? Чому ви сюди не привезли різних варіантів? Маючи всі приклади коду в посиланнях, це навіть заборонений стиль публікації тут на SO Ваш текст виглядає скоріше як купа коментарів.
Gangnus

2
Ви повинні мати можливість вибрати, на якому рівні ви хочете їх спіймати, і потоки возиться з цим. API Stream повинен дозволити вам перенести виняток до остаточної операції (наприклад, збирати) та обробляти там оброблювачем або викидати інакше. stream.map(Streams.passException(x->mightThrowException(x))).catch(e->whatToDo(e)).collect(...). Це очікує винятків і дозволить вам поводитися з ними, як у майбутньому.
aalku

10

Я пропоную скористатися класом Throwables Google Guava

Propagate ( Throwable Throwable)

Поширення, що передається як-є, якщо це екземпляр RuntimeException або Помилка, або в крайньому випадку, загортає його в RuntimeException і потім поширює. **

void bar() {
    Stream<A> as = ...
    as.forEach(a -> {
        try {
            a.foo()
        } catch(Exception e) {
            throw Throwables.propagate(e);
        }
    });
}

ОНОВЛЕННЯ:

Тепер, коли це застаріле використання:

void bar() {
    Stream<A> as = ...
    as.forEach(a -> {
        try {
            a.foo()
        } catch(Exception e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    });
}

4
Цей метод був застарілий (на жаль).
Роберт Важан

9

Ви можете перегорнути та вилучити винятки таким чином.

class A {
    void foo() throws Exception {
        throw new Exception();
    }
};

interface Task {
    void run() throws Exception;
}

static class TaskException extends RuntimeException {
    private static final long serialVersionUID = 1L;
    public TaskException(Exception e) {
        super(e);
    }
}

void bar() throws Exception {
      Stream<A> as = Stream.generate(()->new A());
      try {
        as.forEach(a -> wrapException(() -> a.foo())); // or a::foo instead of () -> a.foo()
    } catch (TaskException e) {
        throw (Exception)e.getCause();
    }
}

static void wrapException(Task task) {
    try {
        task.run();
    } catch (Exception e) {
        throw new TaskException(e);
    }
}

9

Ви можете зробити одну з наступних дій:

  • поширювати перевірений виняток,
  • заверніть його та поширюйте неперевірений виняток, або
  • зловити виняток і припинити розповсюдження.

Кілька бібліотек дозволяють зробити це легко. Приклад нижче написаний за допомогою моєї бібліотеки NoException .

// Propagate checked exception
as.forEach(Exceptions.sneak().consumer(A::foo));

// Wrap and propagate unchecked exception
as.forEach(Exceptions.wrap().consumer(A::foo));
as.forEach(Exceptions.wrap(MyUncheckedException::new).consumer(A::foo));

// Catch the exception and stop propagation (using logging handler for example)
as.forEach(Exceptions.log().consumer(Exceptions.sneak().consumer(A::foo)));

Гарної роботи в бібліотеці! Я збирався написати щось подібне.
Джаспер де Вріс

2

Більш зрозумілий спосіб:

class A {
  void foo() throws MyException() {
    ...
  }
}

Просто прихойте це, RuntimeExceptionщоб пройти йогоforEach()

  void bar() throws MyException {
      Stream<A> as = ...
      try {
          as.forEach(a -> {
              try {
                  a.foo();
              } catch(MyException e) {
                  throw new RuntimeException(e);
              }
          });
      } catch(RuntimeException e) {
          throw (MyException) e.getCause();
      }
  }

Хоча в цей момент я не затримаюсь проти когось, якщо вони скажуть пропустити потоки та йти циклом for, якщо тільки:

  • ви не створюєте свій потік за допомогою Collection.stream(), тобто не прямого перекладу до циклу for.
  • ви намагаєтеся використовувати parallelstream()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.