Перехват исключения, вложенного в другое исключение

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

} catch (RemoteAccessException e) {
    if (e != null && e.getCause() != null && e.getCause().getCause() != null) {
        MyException etrp = (MyException) e.getCause().getCause();
        ...
    } else {
        throw new IllegalStateException("Error at calling service 'service'");
    }
}

Есть ли способ сделать это более эффективным и элегантным?

9 ответов

Решение

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

Элегантное решение - избавиться от вложенности исключений. Либо сначала не объединяйте исключения в цепочки, либо (выборочно) разверните и перебросьте вложенные исключения дальше по стеку.

Исключения имеют тенденцию быть вложенными по 3 причинам:

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

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

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

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

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

В третьем случае вам следует пересмотреть свою стратегию обработки исключений; т.е. сделать это правильно 2.


1 - Действительно, одна из полулегитимных причин для этого исчезла из-за введения синтаксиса перехвата нескольких исключений в Java 7.

2 - Не меняйте свои методы API на throws Exception , Это только усугубляет ситуацию. Теперь вы должны либо "обрабатывать", либо размножаться Exception каждый раз, когда вы вызываете методы. Это рак...

Метод ExceptionUtils#getRootCause() может оказаться очень полезным в таких ситуациях.

Вы должны добавить несколько проверок, чтобы увидеть, если e.getCause().getCause() действительно MyException, В противном случае этот код будет ClassCastException, Я бы, наверное, написал это так:

} catch(RemoteAccessException e) {
    if(e.getCause() != null && e.getCause().getCause() instanceof MyException) {
        MyException ex = (MyException)e.getCause().getCause();
        // Do further useful stuff
    } else {
        throw new IllegalStateException("...");
    }
}

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

  /**
   * Recursive method to determine whether an Exception passed is, or has a cause, that is a
   * subclass or implementation of the Throwable provided.
   *
   * @param caught          The Throwable to check
   * @param isOfOrCausedBy  The Throwable Class to look for
   * @return  true if 'caught' is of type 'isOfOrCausedBy' or has a cause that this applies to.
   */
  private boolean isCausedBy(Throwable caught, Class<? extends Throwable> isOfOrCausedBy) {
    if (caught == null) return false;
    else if (isOfOrCausedBy.isAssignableFrom(caught.getClass())) return true;
    else return isCausedBy(caught.getCause(), isOfOrCausedBy);
  }

Когда вы используете его, вы просто создаете список if от наиболее конкретного исключения до наименее конкретного, с резервным оператором else:

try {
  // Code to be executed
} catch (Exception e) {
  if (isCausedBy(e, MyException.class)) {
    // Handle MyException.class
  } else if (isCausedBy(e, AnotherException.class)) {
    // Handle AnotherException.class
  } else {
    throw new IllegalStateException("Error at calling service 'service'");
  }
}

Я не вижу причин, почему вы хотите, чтобы обработка исключений была эффективной и элегантной, я согласен на эффективную. Они называются исключениями по причине.

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

Первый (e!= Null) не нужен.

И вы можете изменить третий лучше на e.getCause(). GetCause() instanceof MyException)

Вы можете сделать как ниже:

catch (RemoteAccessException e) {
    int index = ExceptionUtils.indexOfThrowable(e, MyExcetption.class)
    if (index != -1) {
         //handleMyException
    } else {
    }
}

Если вы исследуете, вызвано ли исключение настраиваемым исключением (например, MyException) вы можете выполнять итерацию с помощью цикла while, пока не найдете экземпляр MyException.

      boolean isCausedByMyException(Throwable exception) {
    do {
        if (exception instanceof MyException) {
            return true;
        }

        exception = exception.getCause();
    } while (exception != null);

    return false;
}

Я сомневаюсь, но вы можете проверить с instanceof если исключение имеет правильный тип.

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

Я полагаю, вы могли бы также использовать ExceptionUtils.throwableOfThrowable()как здесь

Другие вопросы по тегам