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