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

Почему ловушка проверенных исключений допускается для кода, который не генерирует исключений?

В Java методы, которые бросают проверенные исключения (Exception или его подтипы - IOException, InterruptedException, и т.д.) должен объявить инструкцию throws:

public abstract int read() throws IOException;

Способы, объявляющие throws оператор не могут исключать проверенные исключения.

public int read() { // does not compile
    throw new IOException();
}
// Error: unreported exception java.io.IOException; must be caught or declared to be thrown

Но ловушка проверенных исключений в безопасных методах все еще легальна в java:

public void safeMethod() { System.out.println("I'm safe"); }

public void test() { // method guarantees not to throw checked exceptions
    try {
        safeMethod();
    } catch (Exception e) { // catching checked exception java.lang.Exception
        throw e; // so I can throw... a checked Exception?
    }
}

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

public void test() { // guarantees not to throw checked exceptions
    try {
        safeMethod();
    } catch (Exception e) {        
        throw (Exception) e; // seriously?
    }
}
// Error: unreported exception java.lang.Exception; must be caught or declared to be thrown

Первый фрагмент был мотивом для вопроса.

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


Возвращаясь к главному вопросу - существуют ли какие-либо причины для реализации catching проверенных исключений таким образом? Это всего лишь недостаток дизайна, или я не хватает некоторых важных факторов - возможно, отсталых несовместимостей? Что могло бы пойти не так, если в этом сценарии было разрешено использовать только RuntimeException? Примеры приветствуются.

4b9b3361

Ответ 1

Цитирование Спецификация языка Java, §11.2.3:

Это ошибка времени компиляции, если предложение catch может уловить проверенный класс исключений E1, и это не тот случай, когда блок try, соответствующий предложению catch, может выдать проверенный класс исключений, который является подклассом или суперклассом E1, если E1 является Исключением или суперклассом Исключения.

Я предполагаю, что это правило возникло задолго до Java 7, где многолобы не существовало. Поэтому, если у вас был блок try, который мог бы выкинуть множество исключений, самым простым способом поймать все было бы поймать общий суперкласс (в худшем случае Exception или Throwable, если вы хотите поймать Error).

Обратите внимание, что вы не можете поймать тип исключения, который полностью не связан с тем, что на самом деле выбрано - в вашем примере захват любого подкласса Throwable, который не является RuntimeException, будет ошибкой:

try {
    System.out.println("hello");
} catch (IOException e) {  // compilation error
    e.printStackTrace();
}

<ч/" > Edit by OP: Основная часть ответа заключается в том, что примеры вопросов работают только для класса Exception. Как правило, проверенные исключения исключаются в случайных местах кода. Извините, если я смутил кого-то, использующего эти примеры.

Ответ 2

Java 7 представила более полную проверку типов исключений.

Однако в Java SE 7 вы можете указать типы исключений FirstException и SecondException в предложении throws в объявлении метода rethrowException. Компилятор Java SE 7 может определить, что исключение, созданное выражением throw, должно быть получено из блока try, и единственными исключениями, которые были выбраны блоком try, могут быть FirstException и SecondException.

В этом отрывке говорится о блоке try, который специально выбрасывает FirstException и SecondException; даже если блок catch выбрасывает Exception, метод должен только объявить, что он выбрасывает FirstException и SecondException, а не Exception:

public void rethrowException(String exceptionName)
 throws FirstException, SecondException {
   try {
     // ...
   }
   catch (Exception e) {
     throw e;
   }
 }

Это означает, что компилятор может обнаружить, что единственными возможными типами исключений, которые были выбраны в test, являются Error или RuntimeException s, ни один из которых не нужно поймать. Когда вы throw e;, он может сказать, даже если статический тип Exception, что его не нужно объявлять или перехватывать.

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

Основная причина добавления этой логики в компилятор заключалась в том, чтобы позволить программисту указывать только определенные подтипы в предложении throws при перестройке общего Exception, улавливающего эти конкретные подтипы. Однако в этом случае он позволяет поймать общий Exception и не должен объявлять какое-либо исключение в предложении throws, так как никакие конкретные типы, которые могут быть выбраны, являются проверенными исключениями.

Ответ 3

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

Устранение неконтролируемого исключения с помощью Exception является допустимым, поскольку исключенные исключения (a.k.a. RuntimeException s) являются подклассом Exception и следуют стандартным правилам полиморфизма; он не превращает исключенное исключение в Exception, так же как сохранение String в Object не превращает String в Object. Полиморфизм означает, что переменная, которая может содержать Object, может содержать что-либо, полученное из Object (например, String). Аналогично, поскольку Exception является суперклассом всех типов исключений, переменная типа Exception может содержать любой класс, полученный из Exception, не превращая объект в Exception. Рассмотрим это:

import java.lang.*;
// ...
public String iReturnAString() { return "Consider this!"; }
// ...
Object o = iReturnAString();

Несмотря на то, что тип переменной Object, o все еще сохраняет String, не так ли? Аналогично, в вашем коде:

try {
    safeMethod();
} catch (Exception e) { // catching checked exception
    throw e; // so I can throw... a checked Exception?
}

Это означает, что на самом деле "поймайте что-либо совместимое с классом Exception (т.е. Exception и все, что от него происходит)". Подобная логика используется и на других языках; например, в С++, catch также поймает std::runtime_error, std::logic_error, std::bad_alloc, любые правильно определенные пользовательские исключения и т.д., потому что все они происходят из std::exception.

tl; dr: Вы не ловите проверенные исключения, вы получаете какие-либо исключения. Исключение составляет только проверенное исключение, если вы включили его в выбранный тип исключения.