Разница между Throws в сигнатуре метода и операторами Throw в Java

Я пытаюсь прояснить разницу между Throws в сигнатуре метода и Throw Statements в Java. Броски в сигнатуре метода следующие:

public void aMethod() throws IOException{
    FileReader f = new FileReader("notExist.txt");
}

Заявления Throw выглядят следующим образом:

public void bMethod() {
    throw new IOException();
}

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

Однако следующий код, похоже, этого не делает. Код взят из библиотеки. Мой вопрос: почему это происходит? Я неправильно понимаю понятия?

Этот фрагмент кода является копией из java.util.linkedList. @author Джош Блох

 /**
 * Returns the first element in this list.
 *
 * @return the first element in this list
 * @throws NoSuchElementException if this list is empty
 */
public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

Обновление ответа:

обновление 1: приведенный выше код такой же, как следующий?

// as far as I know, it is the same as without throws
public E getFirst() throws NoSuchElementException {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

обновление 2: для проверенного исключения. Нужно ли иметь в подписи "броски"? да.

// has to throw checked exception otherwise compile error
public String abc() throws IOException{
    throw new IOException();
}

person Weishi Z    schedule 05.10.2013    source источник
comment
Небольшое исправление: оператор throw не создает бросаемый объект; он просто выбрасывает уже созданный объект. Это ключевое слово new, которое создает объект. Думайте о throw new MyException() как о throw (new MyException()). Вы также можете иметь MyException e = new MyException(); throw e. Увидеть разницу? throw бросает, new создает экземпляр.   -  person Bruno Reis    schedule 05.10.2013


Ответы (4)


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

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

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

Но вы были не совсем правы, когда сказали: «Выбросы в сигнатуре метода всегда должны появляться, если в методе существует оператор throw». Часто это так, но не всегда. Я также мог бы вызвать другой метод, который генерирует исключение внутри моего метода, и если я его не перехватываю, мой метод должен его сгенерировать. В этом случае у меня нет явного броска того же исключения.

Наконец, вам нужно объявить исключение в throws только в том случае, если оно является проверенным исключением, то есть оно находится на другой стороне иерархии классов исключений от Исключение во время выполнения. Распространенными проверенными исключениями являются IOException и SQLException. Проверенные исключения должны быть перечислены в части throws сигнатуры метода, если вы не обрабатываете их самостоятельно. Все, что является подклассом RuntimeException, например NoSuchElementException в вашем примере, а также ненавистное исключение NullPointerException, является непроверенным исключением, и его не нужно ловить или бросать или что-то в этом роде.

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

Если вы можете обойти все аспекты АОП, это отличное обсуждение как вы эффективно используете проверенные и непроверенные исключения.

person Vidya    schedule 05.10.2013
comment
Какой смысл перечислять проверенные исключения в «выбросах», если мы их не обрабатываем? Они автоматически ловятся? А в случае NullPointerException, почему его не нужно ловить? Я могу получить это во время выполнения, и мы можем поймать его и выдать простое сообщение об ошибке? - person Diffy; 26.07.2014
comment
NullPointerException будет означать, что в вашем коде есть ошибка. Вы никогда не должны позволять им происходить идеально. - person Kkov; 08.01.2015

Видья дал отличный ответ на ваши вопросы.

Самые важные слова: "И последнее, что вам нужно объявить исключение в бросках, только если это проверенное исключение"

Просто чтобы показать вам пример кода, что это значит. Представьте, что мы хотели бы использовать FileOutputStream для передачи некоторых данных. Функция будет выглядеть так:

public void saveSomeData() throws IOException {
  FileInputStream in = null;
  FileOutputStream out = null;

  try {
    in = new FileInputStream("input.txt");
    out = new FileOutputStream("output.txt");
    int c;

    while ((c = out.read() != -1) {
      in.write(c);
    }
  } catch (Exception e) {
    e.printStackTrace();
  } finally {
    // Close in
    if (in != null) {
      in.close(); // <-- If something bad happens here it will cause runtime error!
    }
    // Close out
    ...
  }
}

А теперь представьте, если бы вы не предоставили throws IOException и что-то плохое произошло внутри оператора finally{}, это вызвало бы ошибку.

person 0leg    schedule 07.02.2015

Атрибут throw в сигнатуре метода, как вы правильно догадались, является подсказкой компилятору о том, что метод вызывает исключение, которое должно быть перехвачено вызывающей стороной. Исключение такого типа, называемое проверенное исключение, вызывающая сторона ДОЛЖНА всегда перехватывать или снова отправлять вызывающей стороне. Это что-то на уровне компилятора, сигнатура указывает, какое исключение метод может генерировать: это обеспечивает try-catch или повторную отправку в вызывающем объекте, а оператор throw где-то внутри метода — это ограничение, которое разработчик накладывает, чтобы указать что-то о поведение метода.

С другой стороны, другие исключения, а именно непроверенные или исключения времени выполнения (например, NoSucheElementException) — это исключения, которые вам не нужно указывать, поскольку они возникают в разных ситуациях.

Концептуальное отличие состоит в том, что проверяемые исключения обычно используются для предупреждения об исключительной ситуации, которая должна каким-то образом обрабатываться (подумайте о IOException) разработчиком, а непроверенные - это настоящие ошибки (например, NullPointerException или как в вашем примере NoSuchElementException)

person Jack    schedule 05.10.2013

RuntimeException не нужно обрабатывать в блоке try-catch, поэтому их не нужно объявлять брошенными и NoSuchElementException имеет значение RuntimeException, потому что оно расширяет его.

person Pshemo    schedule 05.10.2013
comment
Так как же обрабатываются RunTimeExceptions. Разве мы не должны обрабатывать исключения NullPointerException и RunTimeException? - person Diffy; 26.07.2014
comment
@Diffy Вы можете обрабатывать такие исключения с помощью try-catch, но в большинстве случаев лучше просто позволить им остановить приложение, поскольку они, скорее всего, представляют собой ошибку в коде, которую необходимо исправить. - person Pshemo; 26.07.2014