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
из bar
кажется бесполезным здесь. Это почему?
7 ответов
Вам нужно заключить вызов вашего метода в другой, где вы не выбрасываете проверенные исключения. Вы все еще можете бросить все, что является подклассом RuntimeException
,
Нормальная идиома обертки - это что-то вроде:
private void safeFoo(final A a) {
try {
a.foo();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
(Исключение супертипа Exception
используется только в качестве примера, никогда не пытайтесь поймать его самостоятельно)
Тогда вы можете позвонить с помощью: as.forEach(this::safeFoo)
,
Если все, что вы хотите, это вызвать foo
и вы предпочитаете распространять исключение как есть (без переноса), вы также можете просто использовать Java for
вместо этого цикла (после превращения потока в Iterable с некоторыми хитростями):
for (A a : (Iterable<A>) as::iterator) {
a.foo();
}
Это, по крайней мере, то, что я делаю в своих тестах JUnit, где я не хочу испытывать трудности с упаковкой проверенных исключений (и фактически предпочитаю, чтобы мои тесты бросали развернутые исходные)
Этот вопрос может быть немного старым, но, поскольку я думаю, что "правильный" ответ здесь - это только один способ, который может привести к некоторым проблемам, скрытым в дальнейшем в вашем коде. Даже если есть небольшое противоречие, проверенные исключения существуют по причине.
На мой взгляд, самый изящный способ, который вы можете найти, был предложен Мишей. Агрегируйте исключения времени выполнения в потоках Java 8, просто выполняя действия в "будущих". Таким образом, вы можете запустить все рабочие части и собрать неработающие исключения как единое целое. В противном случае вы можете собрать их все в Список и обработать позже.
Аналогичный подход исходит от Бенджи Вебера. Он предлагает создать собственный тип для сбора рабочих и не рабочих частей.
В зависимости от того, что вы действительно хотите добиться простого отображения между входными значениями и выходными значениями, возникшие исключения могут также работать для вас.
Если вам не нравится ни один из этих способов, подумайте об использовании (в зависимости от исходного исключения) как минимум собственного исключения.
Вы можете сделать одно из следующих действий:
- распространять проверенное исключение,
- оберните это и распространите непроверенное исключение, или
- поймать исключение и остановить распространение.
Несколько библиотек позволяют вам сделать это легко. Пример ниже написан с использованием моей библиотеки 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)));
Я предлагаю использовать 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);
}
});
}
ОБНОВИТЬ:
Теперь, когда это устарело, используйте:
void bar() {
Stream<A> as = ...
as.forEach(a -> {
try {
a.foo()
} catch(Exception e) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
});
}
Вы можете обернуть и развернуть исключения таким образом.
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);
}
}
Более читаемый способ:
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()