Почему компилятор Java позволяет перечислять исключения в разделе throws, которые метод не может выбросить

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

Обратите внимание на эти фрагменты кода.

Фрагмент 1

catch исключения, которое никогда не генерируется.

public void g(){
        try {

        } catch (FileNotFoundException e) {//any checked exception

        }

}

Это ошибка компиляции с сообщением

Unreachable catch block for FileNotFoundException. This exception is never thrown from the try statement body

Snippet2

Объявление throws, указывающее на исключение, которое никогда не генерируется.

public void g() throws FileNotFoundException{
}

Компилируется нормально.

Следовательно, результаты первого фрагмента кода показывают, что компилятор может вычислить, может ли метод вызвать исключение, указанное в списке throws. Таким образом, похоже, что компилятор намеренно не сообщает об ошибке для второго фрагмента. Но почему? Почему компилятор позволяет вам записывать исключения в разделе throws, даже если он знает, что эти исключения не могут быть сгенерированы?


person gstackoverflow    schedule 24.03.2014    source источник
comment
Ответ прост: так сказано в спецификации. Немного порассуждая, кажется, что в первом случае вы сталкиваетесь с решением Java сделать недостижимый код ошибкой: блок catch никогда ничего не поймает. Во втором случае, хотя ваш метод никогда не вызовет это исключение, нет кода, который был бы недоступен из-за этого.   -  person dlev    schedule 24.03.2014
comment
@dlev Вы не поняли мой вопрос   -  person gstackoverflow    schedule 24.03.2014
comment
Это может быть случай контракта / интерфейса и реализации. Вы определяете метод, который может вызвать исключение - ›вызывающие абоненты могут захотеть обработать это исключение, если оно возникает. Метод может завершиться нормально, ему не нужно каждый раз генерировать исключение. В этом конкретном случае ваша реализация никогда не будет генерировать исключение, но метод (в любой будущей реализации) все еще может.   -  person Germann Arlington    schedule 24.03.2014
comment
P.S. Недостижимый блок catch похоже на предупреждение, а не на ошибку, потому что код все еще компилируется и запускается ...   -  person Germann Arlington    schedule 24.03.2014
comment
@Admins - это не дубликат stackoverflow.com/questions/22116175/   -  person gstackoverflow    schedule 24.03.2014
comment
@Germann Arlington P.S. Недостижимый блок перехвата похож на предупреждение, а не на ошибку, потому что код все еще компилируется и запускается. создайте основной метод, создайте объект, вызовите метод, и вы получите проблему компиляции   -  person gstackoverflow    schedule 24.03.2014
comment
public class UnreachableCatchBlock { public static void main(String[] args) { UnreachableCatchBlock ucb = new UnreachableCatchBlock(); System.out.println(ucb.getClass().getName() + " started."); ucb.method2(); // ucb.method1(); } private void method1() { try { System.out.println(getClass().getName() + ".method1() started."); } catch (FileNotFoundException e) {//any checked exception } } private void method2() { System.out.println(getClass().getName() + ".method2() started."); } } компилируется и запускается.   -  person Germann Arlington    schedule 24.03.2014
comment
Он терпит неудачу только тогда, когда вы пытаетесь использовать / вызвать метод, который не реализован правильно ...   -  person Germann Arlington    schedule 24.03.2014
comment
@Germann Arlington, напишите, пожалуйста, ответ - я не могу прочитать здесь этот код   -  person gstackoverflow    schedule 24.03.2014


Ответы (5)


Компилятор допускает это, поскольку предложение throws метода является частью сигнатуры метода, а не его реализации. Возможно, что реализация в какой-то момент может измениться при сохранении подпись такая же. Старая реализация могла вызвать проверенное исключение, а новая - нет. Или разработчик подписи мог захотеть предоставить разработчику гибкость для создания проверенного исключения, когда это не всегда необходимо.

person Raedwald    schedule 24.03.2014
comment
Принято с вами. Но хочу увидеть и другие новые взгляды - person gstackoverflow; 24.03.2014
comment
После прочтения всех текущих представлений я хочу добавить к этому ответу, что это частичная разработка по контракту. и я думаю, что упоминание интерфейсов было бы полезно для этого ответа) - person gstackoverflow; 24.03.2014

Просто попробуйте !!

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

person RKC    schedule 24.03.2014
comment
Лисков нарушает правила замены. - person gstackoverflow; 24.03.2014

Поскольку вам нужны были другие взгляды -

Рассмотрим две реализации одной и той же сигнатуры в двух разных подклассах.

Например, (выдуманный пример),

public class StudentLoader {
    public abstract Student readStudentData() throws SQLException, IOException;

    public static void main(String args[]) {
        StudentLoader loader = getStudentLoader (); //may return any subclass instance
        try {
            Student s = loader.readStudentData();
        }
        catch(IOException e) {
            //do something
        }
        catch(SQLException e) {
            //do something
        }
    }
}

public class StudentFileReader extends StudentLoader {
    public Student readStudentData() throws IOException {
        //read from a file
    }
}

public class StudentDBReader extends StudentLoader {
    public Student readStudentData() throws SQLException {
        //read from DB
    }
}

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

Несмотря на то, что реализация подкласса StudentDBReader не выбрасывает IOException, родительский класс StudentLoader все равно должен сказать throws IOException, потому что другие реализации StudentLoader могут его выбросить. Таким образом, даже если исключение не может быть создано методом, у вас есть способ указать вызывающей стороне с помощью ссылки StudentLoader (которая указывает на любой из двух экземпляров подкласса), что вызывающая сторона должна обрабатывать эти исключения.

В вашем фрагменте 1, показывающем метод g(), нет области наследования. Код находится прямо здесь, в блоке try. Если какой-либо оператор в try выбрасывает отмеченное исключение, вам придется его обработать. В случае предложения throws должна быть разрешена возможность наследования. Компилятор не может решить, какая версия readStudentData( ) будет вызываться во время выполнения.

Я ожидал, что компилятор должен выдать ошибку в случае статических методов, если исключение, указанное в предложении throws, не выбрано, поскольку статические методы не участвуют в наследовании. Я не уверен, почему предложение throws в статических методах может содержать исключения, которые никогда не выбрасываются при реализации. В любом случае он не будет отменен, так почему бы не выдать здесь ошибку? Возможно, я что-то упускаю.

person RuntimeException    schedule 24.03.2014

public class UnreachableCatchBlock {

    public static void main(String[] args) {
        UnreachableCatchBlock ucb = new UnreachableCatchBlock();
        System.out.println(ucb.getClass().getName() + " started.");
//      ucb.method1();
        ucb.method2();
    }

    private void method1() {
        try {
            System.out.println(getClass().getName() + ".method1() started.");
        } catch (FileNotFoundException e) {//any checked exception

        }
    }

    private void method2() {
            System.out.println(getClass().getName() + ".method2() started.");
    }

}

Вышеупомянутый код компилируется и запускается, как описано в ответе @Raedwald.

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

person Germann Arlington    schedule 24.03.2014
comment
это очень странно. Для меня это удивительное поведение - person gstackoverflow; 24.03.2014
comment
Как я упоминал ранее и в ответе @Raedwald: подпись позволяет методу генерировать исключения в какой-то момент в будущем и указывает любому коду, использующему метод, что это может произойти. Тот факт, что этого не в настоящий момент, не нарушает договор ... - person Germann Arlington; 24.03.2014
comment
меня удивило, что если прокомментировать ucb.method1 () - я не вижу ошибки компиляции - person gstackoverflow; 24.03.2014
comment
оказывается, это особенность компилятора Eclipse stackoverflow.com/questions/22614515/ - person gstackoverflow; 25.03.2014

Throws не обрабатывает исключение. Он указывает на выброс исключения вверх, откуда будет вызван метод. Другими словами, он просто передаст исключение вызывающей стороне.

Блок try...catch обрабатывает исключение, и поэтому компилятор Java проверяет, есть ли какое-либо исключение для обработки, которое попадает в блок catch или нет.

Это две разные вещи: одна - это бросок, а другая - обработка исключения, и компилятор будет наклонять нос только ко второму ...: p

Из JavaDoc:

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

Итак, предоставляя try...catch реализацию, вы запрашиваете у компилятора нечто большее, чем просто вывод исключения.

Другая конкретная причина:

public void testException() throws FileNotFoundException {
    File file = new File("test.txt");
    System.out.println(file.exists());
    Scanner scanner = new Scanner(file);
}

Если вы посмотрите на скомпилированный код из приведенного выше примера javap -c Test.class, вы обнаружите, что будет создана таблица исключений.

  public static void testException();
    Code:
       0: new           #2                  // class java/io/File
       3: dup           
       4: ldc           #3                  // String test.txt
       6: invokespecial #4                  // Method java/io/File."<init>":(Ljava/lang/String;)V
       9: astore_0      
      10: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      13: aload_0       
      14: invokevirtual #6                  // Method java/io/File.exists:()Z
      17: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V
      20: new           #8                  // class java/util/Scanner
      23: dup           
      24: aload_0       
      25: invokespecial #9                  // Method java/util/Scanner."<init>":(Ljava/io/File;)V
      28: astore_1      
      29: goto          37
      32: astore_1      
      33: aload_1       
      34: invokevirtual #11                 // Method java/io/FileNotFoundException.printStackTrace:()V
      37: return        
    Exception table:
       from    to  target type
          20    29    32   Class java/io/FileNotFoundException

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

и таблица исключений не будет создана в случае throws.

person Not a bug    schedule 24.03.2014
comment
Можете перефразировать второй абзац своего ответа. Я не понимаю. - person gstackoverflow; 24.03.2014