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

Мне интересно, почему компилятор java разрешает броски в объявлении метода, когда метод никогда не будет генерировать исключение. Потому что «броски» — это способ обработки исключения (указание вызывающей стороне обработать его).

Так как есть два способа обработки исключений (throws & try/catch). В try/catch он не позволяет перехватывать исключение, не сгенерированное в блоке try, но позволяет генерировать метод, который может не генерировать исключение.

private static void methodA() {
    try {
        // Do something
        // No IO operation here
    } catch (IOException ex) {  //This line does not compile because
                              //exception is never thrown from try
        // Handle   
    }
}

private static void methodB() throws IOException { //Why does this //compile when excetion is never thrown in function body
    //Do Something 
    //No IO operation
}

person PsyAp    schedule 25.03.2019    source источник
comment
TLDR: это выбор дизайна, и здесь нет правильного или неправильного.   -  person yaseco    schedule 25.03.2019
comment
Поскольку мы не являемся разработчиками языка, почему на вопросы трудно ответить чем-либо, кроме спекулятивных, основанных на мнении ответов.   -  person RealSkeptic    schedule 25.03.2019
comment
Я также считаю, что термин throws вводит в заблуждение. Сейчас я разбираю это как could_throw, и мне кажется, что это лучше соответствует его поведению.   -  person Eric Duminil    schedule 25.03.2019


Ответы (4)


Предложение throws является частью контракта метода. Он требует, чтобы вызывающий метод вел себя так, как будто указанное исключение может быть вызвано методом (т. е. либо перехватывало исключение, либо объявляло собственное предложение throws).

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

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

Первый пример:

Предположим, у вас есть этот код, который использует methodB:

private static void methodA() {
    methodB(); // doesn't have throws IOException clause yet
}

Если позже вы захотите заменить methodB на throw IOException, methodA перестанет проходить компиляцию.

Второй пример:

Предположим, у вас есть этот код, который использует methodB:

private static void methodA() {
    try {
        methodB(); // throws IOException
    }
    catch (IOException ex) {

    }
}

Если вы удалите предложение throws из будущей версии methodB, methodA больше не будет проходить компиляцию.

Этот пример не очень интересен, когда methodA равно private, потому что его можно использовать только локально (внутри того же класса, где легко модифицировать все вызывающие его методы).

Однако, если он становится public, вы не знаете, кто использует (или будет использовать) ваш метод, поэтому вы не контролируете весь код, который может сломаться в результате добавления или удаления предложения throws.

И если это метод экземпляра, есть еще одна причина разрешить предложение throws, даже если вы не выдаете исключение — метод может быть переопределен, а переопределяющий метод может выдать исключение, даже если реализация базового класса этого не делает.

person Eran    schedule 25.03.2019
comment
Последнему пункту о переопределении следует уделить больше внимания, ИМХО. Понятие того, что, если функции изменяются, менее конкретно, чем понятие того, что даже если у родительской функции нет причин генерировать определенные исключения, это может потребоваться даже сегодняшним версиям дочерних функций. - person supercat; 25.03.2019
comment
@supercat согласился, но код в вопросе имеет статические методы, которые нельзя переопределить, поэтому в большинстве моих ответов приводятся причины, применимые к статическим методам. - person Eran; 25.03.2019

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

Предположим, вы пока предоставляете фиктивную реализацию метода, но знаете, что позже фактическая реализация потенциально вызовет исключение IOException. Если бы компилятор запретил вам добавлять это предложение throws, вы были бы вынуждены переработать все вызовы (рекурсивно) этого метода, как только вы предоставите фактическую реализацию метода.

person JB Nizet    schedule 25.03.2019
comment
Спорный вопрос, можно ли сказать, что частный метод имеет контракт. - person RealSkeptic; 25.03.2019
comment
Он определяет контракт для всех остальных методов класса. И эти методы, в свою очередь, могут захотеть объявить IOException, потому что они полагаются на контракт вызываемого ими закрытого метода. - person JB Nizet; 25.03.2019
comment
Вы также можете изменить возвращаемое значение частного метода. Это тоже часть контракта, не так ли? И все же, что вы делаете, когда это происходит, так это исправляете код, который его вызывает. То же самое можно было сделать и с бросками. - person RealSkeptic; 25.03.2019
comment
Я не понимаю, как это связано с вопросом: почему компилятор позволяет иметь предложение throws для исключения, которое не вызывается методом. Какова твоя точка зрения? Компилятор также позволяет вам использовать long в качестве типа возвращаемого значения, даже если вы когда-либо возвращаете только постоянные значения, которые соответствуют типу int. Почему? По той же причине: вы определяете контракт, который заключается в том, что метод может сейчас или позже возвращать длинное значение. - person JB Nizet; 25.03.2019

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

Используйте throws для удобочитаемости и указания возможности исключения, а также используйте try/catch, чтобы сообщить программе, что делать в случае исключения.

person IvanM    schedule 25.03.2019

  1. Метод B выдает IOException, поэтому метод, вызывающий метод B, отвечает за перехват исключения, которое будет сгенерировано методом B. Попробуйте вызвать метод B из других методов, он попросит вас поймать его или повторно сгенерировать исключение IOException. Где-то вам придется поймать IOException в цепочке (в блоке try/catch). Таким образом, вы не получите ошибку времени компиляции.

    private void sampleMethod(){ try { methodB(); } catch (IOException e) { // Автоматически сгенерированный блок catch TODO e.printStackTrace(); } }

  2. try/catch в methodA, по общему признанию, проглатывает исключение, что означает, что methodA отвечает за перехват исключения в блоке try/catch. «Каждый оператор в любой Java-программе должен быть доступен, т. е. каждый оператор должен выполняться хотя бы один раз». Таким образом, вы получите ошибку компилятора, поскольку в вашем блоке try нет кода, вызывающего IOException.

person Harsimran17    schedule 25.03.2019