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

207
Грустно, что мы должны сделать это вместо того, чтобы использовать наши собственные исключения ... о Java, он дает, а затем убирает
Эрих

1
@Stephan Этот ответ был удален, но он все еще доступен здесь: stackoverflow.com/a/27661562/309308
Майкл Мрозек

3
Это сразу же провалило бы проверку кода в моей компании: нам не разрешено создавать непроверенные исключения.
Стелиос Адамантидис

7
@SteliosAdamantidis это правило невероятно ограничительное и контрпродуктивное. Знаете ли вы, что все методы во всех API разрешены для создания всех разновидностей исключений времени выполнения без вашего ведома (конечно, да)? Вы запретили JavaScript для реализации концепции проверенных исключений? Если бы я был вашим ведущим разработчиком, я бы запретил проверенные исключения.
Спи

35

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

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

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


16

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

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

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

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

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


3
Это должно быть помечено как правильный ответ. +. Но я не могу сказать, что это хороший пост. Вы должны тщательно это проработать. Как может помочь собственное исключение? Какие произошли исключения? Почему вы не привели разные варианты здесь? Наличие всего примера кода в ссылках считается даже запрещенным стилем поста здесь, на SO. Ваш текст выглядит скорее как куча комментариев.
Гангнус

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

10

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

Propagate ( Throwable Throwable)

Распространяет 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);
        }
    });
}

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.