Давайте возьмем первую версию вашего кода:
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;
}
}
Будет выведено "готово".