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

Предположим, что у меня есть класс и метод

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

Теперь я хотел бы вызвать foo для каждого экземпляра A, переданного потоком, например:

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

Вопрос: Как правильно обработать исключение? Код не компилируется на моей машине, потому что я не обрабатываю возможные исключения, которые могут быть вызваны foo(). throws Exception of bar представляется бесполезным здесь. Почему это?

Ответ 1

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

Нормальная оберточная идиома - это что-то вроде:

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

(исключение Supertype Exception используется только как пример, никогда не пытайтесь его поймать)

Затем вы можете вызвать его с помощью: as.forEach(this::safeFoo).

Ответ 2

Если вы хотите использовать foo, и вы предпочитаете распространять исключение как есть (без обертывания), вы можете просто использовать цикл Java for вместо этого (после превращения Stream в Iterable с некоторыми обман):

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

Это, по крайней мере, то, что я делаю в своих тестах JUnit, где я не хочу беспокоиться о том, чтобы обернуть мои проверенные исключения (и на самом деле предпочитаю, чтобы мои тесты бросали развернутые оригинальные)

Ответ 3

Этот вопрос может быть немного старым, но поскольку я думаю, что "правильный" ответ здесь - это только один способ, который может привести к некоторым проблемам, скрытым в дальнейшем. Даже если есть небольшая Противоречие, проверенные исключения существуют по какой-либо причине.

Самый элегантный способ, на мой взгляд, вы можете найти здесь, Миша здесь Совокупные исключения времени выполнения в потоках Java 8 просто выполнив действия в "фьючерсах". Таким образом, вы можете запускать все рабочие части и собирать неработающие Исключения как один. В противном случае вы можете собрать их все в списке и обработать их позже.

Аналогичный подход исходит из Benji Weber. Он предлагает создать собственный тип для сбора рабочих и нерабочих частей.

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

Если вам не нравится какой-либо из этих способов, рассмотрите возможность использования (в зависимости от Исходного Исключения) как минимум собственного исключения.

Ответ 4

Я предлагаю использовать класс Google Guava Throwables

распространять (Throwable)

Распространяется как-будто, если он экземпляр RuntimeException или Error, или, в крайнем случае, обертывает он в RuntimeException и затем распространяется. **

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

Ответ 5

Вы можете обернуть и развернуть исключения таким образом.

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

Ответ 6

Возможно, вы захотите выполнить одно из следующих действий:

  • распространять проверенное исключение,
  • обернуть его и распространить непроверенное исключение, или
  • поймать исключение и прекратить распространение.

Несколько библиотек позволяют делать это легко. Пример ниже написан с использованием библиотеки 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)));

Ответ 7

Более читаемый способ:

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()