бросая общее исключение в Java - PullRequest
0 голосов
/ 29 июня 2018

У меня есть еще один сложный вопрос из игры javaDeathMatch; В приведенном ниже коде нас спрашивают, к какой проблеме относится приведенный ниже код. поправьте меня пожалуйста если я не прав; ошибка компиляции: нет; Во время компиляции стирание параметра типа еще не произошло, и динамическое связывание не было выполнено, поэтому параметр, передаваемый методу типа SQLException, рассматривается как исключение в методе 'pleaseThrow', и это (я имею в виду Исключение, а не SQLException) приводится к Runtime Exception в методе без ошибок. Единственная ошибка в том, что у нас нет подходящего предложения catch для отлова исключения.

public class Exption<T extends Exception> {
    public static void main(String[] args) {
        try {
            new Exption<RuntimeException>().pleaseThrow(new SQLException());
        }catch (final SQLException ex){
            ex.printStackTrace();
        }
    }
    private void pleaseThrow(final Exception t) throws T{
        throw (T)t;
    }
}

если мы заменим предложение catch на следующее:

catch(final RuntimeException e){
    e.printStackTrace();
    System.err.println("caught");
}

исключение будет перехвачено, но System.err.println ("пойман") никогда не будет напечатан !!! В чем проблема ????

Ответы [ 3 ]

0 голосов
/ 29 июня 2018

Этот код не удастся скомпилировать, поскольку SQLException является проверенным исключением, и для того, чтобы перехватить проверенное исключение, оно должно быть объявлено как выброшенное чем-то внутри блока try. Здесь не удается скомпилировать на Ideone , например, следующее сообщение:

Main.java:7: error: exception SQLException is never thrown in body of corresponding try statement
        }catch (final SQLException ex){
         ^
Note: Main.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

Если вы измените блок catch, чтобы он ловил RuntimeException, код скомпилируется, но исключение будет , а не , потому что SQLException не является подклассом RuntimeException.

Здесь обсуждается, как здесь работает метод pleaseThrow. Эта идиома обычно называется «подлый бросок», и она позволяет вызывающему объекту генерировать проверенное исключение, как если бы оно было непроверенным. О разнице между отмеченными и непроверенными исключениями см. Официальный учебник или , эти вопросы и ответы и StackOverflow .

0 голосов
/ 22 июля 2018

Давайте возьмем первую версию вашего кода:

public class Exption<T extends Exception> {
    public static void main(String[] args) {
        try {
            new Exption<RuntimeException>().pleaseThrow(new SQLException());
        }catch (final SQLException ex){ // This is compilation error
            ex.printStackTrace();
        }
    }
    private void pleaseThrow(final Exception t) throws T{
        throw (T)t;
    }
}

Как компилятор может обнаружить ошибку?

Это не из-за throw (T)t;. Фактически, если вы наберете приведение к параметру типа, компилятор игнорирует его и оставляет его JVM.

Тогда как компилятор может генерировать ошибку?

Именно из-за этого:

private void pleaseThrow(final Exception t) throws T

Обратите внимание на throws T. Когда вы говорите new Exption<RuntimeException>(), компилятор не создает никаких объектов. Но он выводит T во время компиляции. Подходит под свои полномочия.

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

Компилятор из private void pleaseThrow(final Exception t) throws T{ знает, что вы выбросите RuntimeException.

Согласно правилу приведения типов, компилятор проверяет оба типа, если один является родителем другого. Если да, он передает код в JVM. Затем только JVM будет дополнительно проверять, может ли один объект быть приведен к другому типу.

Аналогичным образом, компилятор проверяет броски и блок catch, если какой-либо из них совместим или нет. Если более чем один совместим, решение остается за JVM. Компилятор проверяет и многие другие вещи, но давайте сосредоточимся на основном треке.

В вашем примере один тип - SqlException, а другой - RuntimeException. Никто не является родителем другого. Следовательно, компилятор показывает ошибку.

Давайте рассмотрим несколько примеров, чтобы очистить его:

public class Exption<T extends Exception> {
    public static void main(String[] args) {
        try {
            new Exption<RuntimeException>().pleaseThrow(new IllegalArgumentException());
        }catch (final ClassCastException ex){
            ex.printStackTrace();
            System.out.println("done");
        }
    }
    private void pleaseThrow(final Exception t) throws T{
        throw (T)t;
    }
}

В этом примере Предложение Catch не будет называться , однако код компилируется нормально. Поскольку ClassCastException является RuntimeException, компилятор прекрасно компилирует код. RuntimeException является родителем для ClassCastException. Но когда компилятор передает код JVM, JVM знает, что объект исключения имеет тип IllegalArgumentException и, следовательно, согласится на предложение catch для IllegalArgumentException или его супертипа. Здесь у нас ничего такого нет. Следовательно, предложение Catch вызываться не будет, так как совпадений нет.

Возьмите другой пример:

public class Exption<T extends Exception> {
    public static void main(String[] args) {
        try {
            new Exption<RuntimeException>().pleaseThrow(new IllegalArgumentException());
        }catch (final RuntimeException ex){
            ex.printStackTrace();
            System.out.println("done");
        }
    }
    private void pleaseThrow(final Exception t) throws T{
        throw (T)t;
    }
}

Это работает нормально, и блок catch называется . JVM знает, что тип объекта будет IllegalArgumentException, а RuntimeException является суперклассом, следовательно, он соответствует из-за того, что суперкласс может ссылаться на объект дочернего класса.

Теперь давайте вернемся к вашему коду.

Вы записали только один блок перехвата, состоящий из SqlException, который является checked exception и, следовательно, не может быть сгенерирован из pleaseThrow(), поскольку он генерирует RuntimeException в соответствии с компилятором.

Итак, эта ошибка генерируется:

Error:(9, 10) java: exception java.sql.SQLException is never thrown in body of corresponding try statement

Как вы знаете, в Java запрещено, чтобы блок catch, перехватывающий проверенное выражение, никогда не выдавался.


Теперь давайте перейдем к версии 2 вашего кода:

public class Exption<T extends Exception> {
    public static void main(String[] args) {
        try {
            new Exption<RuntimeException>().pleaseThrow(new SQLException());
        }catch(final RuntimeException e){
            e.printStackTrace();
            System.err.println("caught");
        }
    }
    private void pleaseThrow(final Exception t) throws T{
        throw (T)t;
    }
}

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

Теперь давайте посмотрим внимательнее. Компилятор легко компилирует этот код и отправляет его в JVM. Но перед этим он стирает тип.

Давайте посмотрим, что бы стирало тип, сделавшее с нашим кодом: [В реальном коде, переданном JVM, указан код уровня байтов. Приведенный ниже пример показывает, что тип стирает с кодом. Обратите внимание, что приведенный ниже код не будет работать должным образом в компиляторе, если вы попытаетесь скомпилировать, так как этот код выполняется после стирания типа и потеряны некоторые детали, которые нужны компилятору. Однако JVM может выполнить эквивалентный байтовый код этого.]

public class Exption {
    public static void main(String[] args) {
        try {
            new Exption().pleaseThrow(new SQLException());
        }catch(final RuntimeException e){
            e.printStackTrace();
            System.err.println("caught");
        }
    }
    private void pleaseThrow(final Exception t) throws java.lang.Exception {
        throw (java.lang.Exception) t;
    }
}

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

Теперь этот код - очень интересный кусок кода.

По этому коду напрямую работает JVM. Если вы видите, что JVM знает, что метод выдает объект типа SqlException, приведенный к исключению. Он пытается найти совпадение в блоках перехвата, но не соответствует RunTimeException не является суперклассом SqlException. Следовательно, блок catch не вызывается.

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

public class Exption<T extends Exception> {
    public static void main(String[] args) {
        try {
            new Exption<RuntimeException>().pleaseThrow(new SQLException());
        }catch(final RuntimeException e){
            e.printStackTrace();
            System.err.println("caught");
        }catch(Exception r){
            System.out.println("done");
        }

    }
    private void pleaseThrow(final Exception t) throws T{
        throw (T)t;
    }
}

Будет выведено "готово".

0 голосов
/ 29 июня 2018

Это связано с удалением типа. В java после компиляции каждая общая информация теряется (остается что-то, что, однако, не имеет к этому отношения). Это означает, что во время компиляции общая переменная T равна RuntimeException. Итак, ваш код pleaseThrow выглядит следующим образом:

private void pleaseThrow(final Exception t) throws RuntimeException{
    throw (RuntimeException)t;
}

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

private void pleaseThrow(final Exception t) throws Exception{
    throw (Exception)t;
}

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

Дополнительное чтение: Учебное пособие по Oracle по стиранию типов

...