Есть ли способ заставить Runnable run () генерировать исключение?

80

Метод, который я вызываю в run () в классе, который реализует Runnable ), предназначен для генерации исключения.

Но компилятор Java не позволяет мне этого делать и предлагает окружить его командой try / catch.

Проблема в том, что, окружая его попыткой / catch, я делаю этот конкретный run () бесполезным. Я действительно хочу , чтобы бросить это исключение.

Если я укажу throwsдля самого run () , компилятор пожалуется на это Exception is not compatible with throws clause in Runnable.run().

Обычно я совершенно нормально не позволяю run () генерировать исключение. Но у меня уникальная ситуация, в которой я должен иметь эту функциональность.

Как обойти это ограничение?

Regex Rookie
источник
В дополнение к другим ответам для отслеживания прогресса задачи вы можете использовать класс FutureTask.
JProgrammer
1
Вопрос о Java, не относящийся к Android: stackoverflow.com/questions/1369204/…
Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功

Ответы:

26

Если вы хотите передать класс, который реализуется Runnableво Threadфреймворке, вы должны играть по правилам этого фреймворка, см. Ответ Эрнеста Фридман-Хилла, почему делать это в противном случае - плохая идея.

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

Ответ на эту проблему прост. Не используйте Runnableинтерфейс из библиотеки потоков, а вместо этого создайте свой собственный интерфейс с измененной подписью, которая позволяет генерировать проверенное исключение, например

public interface MyRunnable
{
    void myRun ( ) throws MyException;
}

Вы даже можете создать адаптер, который преобразует этот интерфейс в реальный Runnable(путем обработки отмеченного исключения), подходящий для использования в среде Thread.

Александр Погребняк
источник
К такому простому решению пришли, просто не думая «в коробке». Конечно, это Runnableпростой интерфейс, и мы можем сделать его самостоятельно. Бесполезно для сценария использования потока, но для передачи различных «исполняемых» фрагментов кода это идеально.
Ричард Ле Мезурье
82

CallableВместо этого вы можете использовать a , отправив его ExecutorServiceи дождавшись результата, FutureTask.isDone()возвращенного ExecutorService.submit().

Когда isDone()возвращается true, вы звоните FutureTask.get(). Теперь, если вы Callableбросили ExceptionтогдаFutureTask.get() тоже выбросит Exceptionи исходное исключение, к которому вы сможете получить доступ, используя Exception.getCause().

Александр Куляхтин
источник
23

Если бы run()возникло проверенное исключение, что бы его поймать? У вас нет возможности заключить этоrun() вызов в обработчик, поскольку вы не пишете код, который его вызывает.

Вы можете перехватить проверенное исключение в run()методе и сгенерировать неотмеченное исключение (т. Е.RuntimeException его место ). Это завершит поток с трассировкой стека; возможно, это то, что вам нужно.

Если вместо этого вы хотите, чтобы ваш run()метод где-то сообщал об ошибке, вы можете просто предоставить метод обратного вызова для вызова блока run()метода catch; этот метод мог бы где-то сохранить объект исключения, и тогда ваш заинтересованный поток мог бы найти объект в этом месте.

Эрнест Фридман-Хилл
источник
2
Первая часть - не лучший аргумент. «Если main()сгенерировать проверенное исключение, что его поймает?» "Если run()сгенерировать непроверенное исключение, что его поймает?"
Christian Hujer
17

Да, есть способ выбросить проверенное исключение изrun() метода, но он настолько ужасен, что я не буду им делиться.

Вот что вы можете сделать вместо этого; он использует тот же механизм, что и исключение времени выполнения:

@Override
public void run() {
  try {
    /* Do your thing. */
    ...
  } catch (Exception ex) {
    Thread t = Thread.currentThread();
    t.getUncaughtExceptionHandler().uncaughtException(t, ex);
  }
}

Как отмечали другие, если ваш run()метод действительно является цельюThread , нет смысла генерировать исключение, потому что оно ненаблюдаемо; генерирование исключения имеет тот же эффект, что и отсутствие исключения (нет).

Если это не Threadцель, не используйте Runnable. Например, возможно, Callableлучше подходит.

Эриксон
источник
Но вызывает ли это сбой процесса, когда он бросает ??
Динеш
@DineshVG Нет, только ошибка в JVM может вызвать настоящий сбой. Обработчик исключений по умолчанию просто печатает исключение. Если вы привыкли видеть завершение процесса после этого, это потому, что этот поток был единственным запущенным потоком, и он завершился.
erickson
Я пробовал (в тестовых примерах Android) использовать это для вызова мыла, где, если я получаю 400 от вызова Soap, я генерирую исключение. Этот вызов мыла вызывается из потока при запуске тестового примера. Этот поток использует это, t.getUncaughtExceptionHandler().uncaughtException(t, ex);чтобы передать его в тестовый пример инструментария. Добавление этой одной строки вызывает сбой процесса !. Не знаю почему.
Динеш
@DineshVG В этой среде Thread.getDefaultUncaughtExceptionHandler()возвращается null? Если нет, то каков результат? Что произойдет, если вместо вызова uncaughtException()вы оберните проверенное исключение в a RuntimeExceptionи выбросите его?
erickson
1
@DineshVG Android может устанавливать обработчик неперехваченных исключений по умолчанию, который делает это. Вот почему я спросил, Thread.getDefaultUncaughtExceptionHandler()возвращается ли null; в противном случае Android предоставляет значение по умолчанию, чтобы предлагать отчеты и т. д. Но вы можете настроить его на то, что хотите. Здесь больше информации .
erickson
6
@FunctionalInterface
public interface CheckedRunnable<E extends Exception> extends Runnable {

    @Override
    default void run() throws RuntimeException {
        try {
            runThrows();
        }
        catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    void runThrows() throws E;

}
Сина Мадани
источник
1

Некоторые люди пытаются убедить вас, что вы должны играть по правилам. Слушайте, а подчиняться ли вы, решать вам в зависимости от ситуации. На самом деле «вы ДОЛЖНЫ играть по правилам» (а не «вы ДОЛЖНЫ играть по правилам»). Просто имейте в виду, что если вы не будете играть по правилам, могут быть последствия.

Ситуация применяется не только в случае Runnable, но и с Java 8 очень часто в контексте потоков и других мест, где функциональные интерфейсы были введены без возможности иметь дело с проверенными исключениями. Например, Consumer, Supplier, Function, BiFunctionи так далее все были объявлены без средств для борьбы с проверяемыми исключениями.

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

  1. Вы Runnableсами где-то заявили , и можете заменить Runnableна что-нибудь другое.
    1. Рассмотрите возможность замены Runnableна Callable<Void>. По сути то же самое, но с возможностью исключения исключений; и вынужден return nullв конце концов, что немного раздражает.
    2. Рассмотрите возможность замены Runnableна свой собственный, @FunctionalInterfaceкоторый может выдавать именно те исключения, которые вам нужны.
  2. Вы использовали API, и есть альтернативы. Например, некоторые API-интерфейсы Java перегружены, поэтому вы можете использовать Callable<Void>вместо Runnable.
  3. Вы использовали API, и альтернативы нет. В этом случае у вас все еще есть варианты.
    1. Вы можете обернуть исключение в RuntimeException.
    2. Вы можете преобразовать исключение в исключение RuntimeException с помощью непроверенного приведения.

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

@FunctionalInterface
public interface ThrowingRunnable extends Runnable {
    @Override
    default void run() {
        try {
            tryRun();
        } catch (final Throwable t) {
            throwUnchecked(t);
        }
    }

    private static <E extends RuntimeException> void throwUnchecked(Throwable t) {
        throw (E) t;
    }

    void tryRun() throws Throwable;
}

Я предпочитаю это, new RuntimeException(t)потому что у него более короткая трассировка стека.

Теперь вы можете:

executorService.submit((ThrowingRunnable) () -> {throw new Exception()});

Отказ от ответственности: возможность выполнять неконтролируемое приведение таким образом может фактически быть удалена в будущих версиях Java, когда информация о типах обобщенных типов обрабатывается не только во время компиляции, но и во время выполнения.

Кристиан Худжер
источник
0

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

Кумар Бибек
источник
0

Я думаю, что в этом сценарии вам может помочь шаблон слушателя . В случае возникновения исключения в вашем run()методе используйте блок try-catch и отправьте в catch уведомление о событии исключения. А затем обработайте событие уведомления. Я думаю, это был бы более чистый подход. Эта ссылка SO дает вам полезный указатель в этом направлении.

Суджай
источник
-1

Самый простой способ - определить собственный объект исключения, который расширяет RuntimeExceptionкласс вместо Exceptionкласса.

Joas
источник
И как бы вы получили это исключение RuntimeException, когда оно возникает, мой дорогой сэр?
Соня
Одно это не дает ответа на вопрос.
Карл Рихтер