Почему Exception.fillInStackTrace возвращает Throwable? - PullRequest
5 голосов
/ 08 января 2010

Я думаю, что Exception.fillInStackTrace должен возвращать Exception или производные Exception объекты. Учитывая две функции ниже,

public static void f() throws Throwable {
    try {
        throw new Throwable();
    } catch (Exception e) {
        System.out.println("catch exception e");
        e.printStackTrace();
    } 
}
public static void g() throws Throwable {
    try {
        try {
            throw new Exception("exception");
        } catch (Exception e) {
            System.out.println("inner exception handler");
            throw e.fillInStackTrace();
        }
    } catch (Exception e) {
        System.out.println("outer exception handler");
        e.printStackTrace();
    }
}
  1. exception handler не удалось поймать new Throwable() в первой функции f().
  2. exception handler может перехватить e.fillInstackTrace() во второй функции g().
  3. Но вторая функция g() все равно должна будет throws Throwable. Это действительно странно, так как мы могли поймать e.fillInstackTrace().

Итак, мой вопрос: почему Exception.fillInStackTrace не возвращает Exception или Exception-based вместо разработки такого странного синтаксиса?

EDIT :
Чтобы уточнить мой вопрос: под "странным синтаксисом" я подразумеваю

  1. Поскольку Exception.fillInStackTrace() возвращает ссылку Throwable, обработчик исключений, который получает ссылку Exception, не должен иметь возможность перехватить исключение. Поскольку java не разрешает понижение значения, это должно быть что-то вроде return (Exception)e.fillInstackTrace().
  2. Поскольку предполагается, что обработчик исключений, получающий ссылку Exception, может обрабатывать исключение Throwable, нет необходимости отмечать метод g() throws Throwable exception.But java-компилятор заставит нас сделать это .

спасибо.

Ответы [ 6 ]

12 голосов
/ 08 января 2010

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

В Java все исключения (в том смысле, что этот термин используется в спецификации языка Java) являются экземплярами некоторого класса, который является подклассом java.lang.Throwable. Есть два (и только два) прямых подкласса Throwable; то есть java.lang.Exception и java.lang.Error. Экземпляры всех этих классов ... включая экземпляры Throwable и Error ... называются исключениями в JLS.

Обработчик исключений перехватывает исключения (в смысле JLS), совместимые по присваиванию с типом исключения, используемым в объявлении catch. Так, например:

try {
    ....
} catch (Exception ex) {
    ...
}

будет перехватывать любое исключение, выброшенное в блоке try, который является экземпляром java.lang.Exception или прямого или косвенного подтипа java.lang.Exception. Но он не поймает экземпляр java.lang.Throwable, потому что это (очевидно) не один из вышеперечисленных.

С другой стороны:

try {
    ....
} catch (Throwable ex) {
    ...
}

будет ловить экземпляр java.lang.Throwable.

Рассматривая ваш пример в свете этого, становится очевидным, почему метод f не перехватывает экземпляр Throwable: он не соответствует типу исключения в предложении catch! Напротив, в методе g экземпляр Exception соответствовал типу исключения в предложении catch и поэтому перехватывается.

Я не понимаю, что вы говорите о необходимости бросить Throwable в g. Во-первых, тот факт, что метод объявляет, что он выбрасывает Throwable , не означает , что означает, что он действительно должен бросить его. Все, что он говорит, это то, что он может генерировать что-то назначаемое для Throwable ... возможно, в какой-то будущей версии g метода. Во-вторых, если вы добавите throw e; к внешнему блоку перехвата, будет создавать что-то, что можно назначить на Throwable.

Наконец, вообще плохая идея - создавать экземпляры Throwable, Exception, Error и RuntimeException. И вам нужно быть очень осторожным, когда и как вы их поймаете. Например:

try {
     // throws an IOException if file is missing
    InputStream is = new FileInputStream("someFile.txt");
    // do other stuff
} catch (Exception ex) {
    System.err.println("File not found");
    // WRONG!!!  We might have caught some completely unrelated exception;
    // e.g. a NullPointerException, StackOverflowError, 
}

РЕДАКТИРОВАТЬ - в ответ на комментарии ОП:

Но что я выбрасываю с помощью throw e.fillInStackTrace (); должен быть Момент Броска, а не Исключение!

В Javadoc говорится, что возвращаемый объект является объектом исключения, для которого вызывается метод. Цель метода fillInStacktrace() - заполнить трассировку стека для существующего объекта. Если вы хотите другое исключение, вы должны использовать new для его создания.

На самом деле, я имею в виду, что обработчик исключений outter не должен перехватывать Throwable, созданный броском e.fillInStackTrace ().

Я объяснил, почему это так - потому что Throwable на самом деле является первоначальным исключением. В моем объяснении есть что-то, что вы не понимаете или просто говорите, что вам не нравится способ определения Java?

РЕДАКТИРОВАТЬ 2

И если обработчик исключений outter мог обработать исключение Throwable, почему мы должны указать, что метод g сгенерирует исключение Throwable

Вы неправильно поняли то, что я говорил ... а именно, что если вы ДЕЙСТВИТЕЛЬНО сгенерировали исключение, то throws Throwable не будет лишним. ОТО, я наконец-то думаю, что понимаю вашу жалобу.

Я думаю, что суть вашей жалобы в том, что вы получите ошибку компиляции с этим:

public void function() throws Exception {
    try {
        throw new Exception();
    } catch (Exception ex) {
        throw ex.fillInStackTrace();
        // according to the static type checker, the above throws a Throwable
        // which has to be caught, or declared as thrown.  But we "know" that the 
        // exception cannot be anything other than an Exception.
    }
}

Я вижу, что это несколько неожиданно. Боюсь, это неизбежно. НЕТ (если не считать серьезных изменений в системе типов Java) НЕТ, чтобы вы могли объявить подпись fillInStacktrace, которая будет работать во всех случаях. Например, если вы переместили объявление метода в класс Exception, вы просто повторили бы ту же проблему с подтипами Exception. Но если вы попытаетесь выразить подпись с помощью параметра универсального типа, это повлечет за собой создание всех подклассов явных универсальных типов Throwable.

К счастью, лечение действительно простое; приведите результат fillInStacktrace() следующим образом:

public void function() throws Exception {
    try {
        throw new Exception();
    } catch (Exception ex) {
        throw (Exception) (ex.fillInStackTrace());
    }
}

И последнее, что для приложения очень необычно явно вызывать fillInStacktrace(). В свете этого для разработчиков Java просто не стоило бы «ломать голову», пытаясь решить эту проблему. Тем более, что это действительно незначительное неудобство ... самое большее.

6 голосов
/ 14 января 2010

На самом деле легче ответить на ваши вопросы, начиная с вопроса 2.

Вы спросили: 2. Поскольку он разработан так, что обработчик исключений, получающий ссылку Exception, может обрабатывать исключение Throwable, нет необходимости отмечать метод g (), генерирующий исключение Throwable. Но компилятор java заставил бы нас сделать это.

Ответ: На самом деле, catch (исключение e) не может поймать Throwable. Попробуйте это:

try {
       Throwable t = new Throwable();
       throw t.fillInStackTrace();
} catch (Exception e) {
    System.out.println("outer exception handler");
    e.printStackTrace();
} 

В этом случае вы увидите, что предложение catch не обрабатывает бросок.

Причина, по которой предложение catch работает в вашем методе g (), заключается в том, что когда вы вызываете throw e.fillInStackTrace(), вызов fillInStackTrace фактически возвращает исключение (это потому, что e само является исключением). Поскольку Exception является подклассом Throwable, это не противоречит объявлению fillInStackTrace.

Теперь к первому вопросу

Вы спросили: 1. Поскольку Exception.fillInStackTrace () возвращает ссылку Throwable, обработчик исключений, который получает ссылку Exception, не должен иметь возможность перехватывать исключение. Поскольку java не разрешает имплицит вниз, это должно быть что-то вроде return (Exception) .

Ответ: Это не совсем неявное удручение. Думайте об этом как о варианте перегрузки.

Допустим, у вас есть

void process(Throwable t){
   ...
}
void process(Exception e){
  ...
} 

Если вы вызовете process(someObject), во время выполнения будет определено, будет ли вызван первый или второй метод процесса. Точно так же, может ли условие catch (Exception e) отловить ваш бросок, будет определено во время выполнения, в зависимости от того, бросили ли вы Exception или Throwable.

4 голосов
/ 13 января 2010

Я думаю, вам нужно спросить Фрэнка Йеллина (@author из java.lang.Exception). Как уже говорили другие, fillInStackTrace() объявлено в Throwable и задокументировано для возврата this, поэтому его тип возврата должен быть Throwable. Это просто наследуется Exception. Фрэнк мог переопределить это в Exception, например:

public class Exception extends Throwable{
    /**Narrows the type of the overridden method*/
    @Override
    public synchronized Exception fillInStackTrace() {
        super.fillInStackTrace();
        return this;
    }
}

... но он этого не сделал. До Java 1.5 причина была в том, что это приводило к ошибке компиляции. В версии 1.5 и выше допускаются ковариантные типы возврата , а приведенное выше допустимо.

Но я предполагаю, что, если вышеупомянутое переопределение существовало, вы бы тогда спросили, почему RuntimeException не имеет аналогичного переопределения, почему ArrayStoreException не переопределяет это переопределение и так далее. Так что Фрэнк и друзья, вероятно, просто не хотели писать эти сотни одинаковых переопределений.

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

Используя дженерики, можно представить расширение объявления Throwable до:

public class Throwable<T extends Throwable<T>>{
    public synchronized native T fillInStackTrace();
}

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

3 голосов
/ 08 января 2010

fillInStackTrace возвращает ссылку на тот же объект. Это метод цепочки, позволяющий повторно выдать Exception и сбросить трассировку стека исключения.

public static void m() {
    throw new RuntimeException();

}

public static void main(String[] args) throws Throwable {
    try {
        m();
    } catch (Exception e) {
        e.printStackTrace();
        throw e.fillInStackTrace();
    }
}

Тип возвращаемого значения метода может быть только базовым, равным Throwable. Его можно обобщить, чтобы метод возвращал параметризованный тип. Но сейчас это не так.

RuntimeException e = new RuntimeException();
Throwable e1 = e.fillInStackTrace();
System.out.println(e1.getClass().getName()); //prints java.lang.RuntimeException
System.out.println(e == e1); //prints true
2 голосов
/ 11 января 2010

На самом деле fillInStackTrace возвращает тот же объект, для которого он был вызван.

e.fillInStackTrace == e всегда верно

Это просто ярлык , вы также можете просто написать

    } catch (Exception e) {
        System.out.println("inner exception handler");
        e.fillInStackTrace();
        throw e;
    }

или используйте приведение

    throw (Exception) e.fillInStackTrace();

Кстати, то же самое происходит с initCause().

2 голосов
/ 08 января 2010

fillInStackTrace() определяется как Throwable, а не Exception.

Не все подклассы Throwable являются исключениями (например, Error). Что касается Throwable, то единственное, что он может гарантировать относительно возвращаемого значения fillInStackTrace(), - это то, что он является экземпляром Throwable (поскольку он просто возвращает тот же объект, что и Чандра Патни отметил).

...