Подтвердить что ты не робот

Безопасно ли хранить экземпляр исключения и повторно использовать его?

Безопасно ли:

public class Widget {

    private static final IllegalStateException LE_EXCEPTION
            = new IllegalStateException("le sophisticated way");

    ...

   public void fun() {
      // some logic here, that may throw
      throw LE_EXCEPTION;
   }

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

вместо того, чтобы бросать исключение new каждый раз?

Мне интересно, безопасно ли это

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

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

4b9b3361

Ответ 1

Это зависит от вашего определения "безопасный". Исключение даст ложную трассировку стека, которую я бы не назвал "безопасным". Рассмотрим:

public class ThrowTest {
    private static Exception e = new Exception("t1"); // Line 2

    public static final void main(String[] args) {
        ThrowTest tt;

        tt = new ThrowTest();
        try {
            tt.t1();
        }
        catch (Exception ex) {
            System.out.println("t1:");
            ex.printStackTrace(System.out);
        }
        try {
            tt.t2();                                  // Line 16
        }
        catch (Exception ex) {
            System.out.println("t2:");
            ex.printStackTrace(System.out);
        }
    }

    private void t1() 
    throws Exception {
        throw this.e;
    }

    private void t2() 
    throws Exception {
        throw new Exception("t2");                    // Line 31
    }
}

У этого вывода есть:

$ java ThrowTest
t1:
java.lang.Exception: t1
    at ThrowTest.<clinit>(ThrowTest.java:2)
t2:
java.lang.Exception: t2
    at ThrowTest.t2(ThrowTest.java:31)
    at ThrowTest.main(ThrowTest.java:16)

Обратите внимание, как метод t1 полностью отсутствует в трассировке стека в первом тестовом случае. Нет никакой полезной контекстной информации вообще.

Теперь вы можете использовать fillInStackTrace для заполнения этой информации непосредственно перед броском:

this.e.fillInStackTrace();
throw this.e;

... но это просто делает работу для себя (работа, которую вы иногда забудете). Просто нет никакой пользы от этого вообще. И не все исключения позволяют это сделать (некоторые исключения делают трассировку стека только для чтения).


Вы сказали в других комментариях, что это нужно избегать "дублирования кода". У вас много улучшена функция создания исключения:

private IllegalStateException buildISE() {
    return new IllegalStateException("le sophisticated way");
}

(Может быть static final, если хотите.)

И затем брось так:

throw buildISE();

Это позволяет избежать дублирования кода без ошибочных стеков стека и ненужных экземпляров Exception.

Вот что это похоже на приведенное выше:

public class ThrowTest {

    public static final void main(String[] args) {
        ThrowTest tt;

        tt = new ThrowTest();
        try {
            tt.t1();                                   // Line 8
        }
        catch (Exception ex) {
            System.out.println("t1:");
            ex.printStackTrace(System.out);
        }
        try {
            tt.t2();                                   // Line 15
        }
        catch (Exception ex) {
            System.out.println("t2:");
            ex.printStackTrace(System.out);
        }
    }

    private static final Exception buildEx() {
        return new Exception("t1");                    // Line 24
    }

    private void t1() 
    throws Exception {
        throw buildEx();                               // Line 29
    }

    private void t2() 
    throws Exception {
        throw new Exception("t2");                     // Line 34
    }
}
$ java ThrowTest
t1:
java.lang.Exception: t1
    at ThrowTest.buildEx(ThrowTest.java:24)
    at ThrowTest.t1(ThrowTest.java:29)
    at ThrowTest.main(ThrowTest.java:8)
t2:
java.lang.Exception: t2
    at ThrowTest.t2(ThrowTest.java:34)
    at ThrowTest.main(ThrowTest.java:15)

Ответ 2

Это небезопасно, если исключение не может быть неизменным (т.е. enableSuppression = writableStackTrace = false).

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

Удивительно, Throwable действительно потокобезопасен, для бог-знает-что. Таким образом, по крайней мере, не будет катастрофического сбоя, если исключение будет изменено несколькими потоками. Но будет логический сбой.

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

Ответ 3

Еще одна причина не в том, что трассировка стека будет неуместной.

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

Таким образом, вы (или кто бы ни использовал ваш код) не смогут определить, где на самом деле лежит ошибка.

Ответ 4

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

Поскольку нет причин для повторного использования экземпляров исключения, я не рекомендую вам это делать.

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

Ответ 5

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

Ответ 6

Я думаю, что это неправильно, потому что это будет побуждать вас использовать исключения использования для описания нормальных ситуаций и не только исключительные. См. "Эффективное Java второе издание" Дж. Блоха, п. 57, стр. 241.

Также вы всегда держите объект в куче, поэтому это необязательно, потому что создание объектов происходит очень быстро в моделях JVM, а также после того, как будет выбрано исключение, вероятно, будет очень быстро собрано мусор.

Также ваш код может стать очень вводящим в заблуждение, что добавляет много накладных расходов для чего-то довольно стремительного.