44

我试图弄清楚方法签名中的抛出和Java 中的抛出语句之间的区别。方法签名中的抛出如下:

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

抛出语句如下:

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

据我了解,throwsin 方法签名是该方法可能抛出此类异常的通知。throw语句是在相应情况下实际抛出创建的对象。从这个意义上说,如果方法中存在throw语句,则方法签名中的throws应始终出现。

但是,以下代码似乎没有这样做。代码来自图书馆。我的问题是为什么会这样?我理解的概念错了吗?

这段代码是 java.util.linkedList 的副本。@作者乔什·布洛赫

 /**
 * 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();
}
4

4 回答 4

35

你说得很对。除了我稍后会提到的一件事。

throws与名称和参数一样是方法 API 的一部分。客户知道如果他们调用了那个方法,他们需要处理那个异常——通过简单地也抛出它或者捕获它并处理它(这实际上可能需要抛出另一个包装原始异常的异常)。throws在编译时处理。

throw是让运行时知道发生了不好的事情的实际行为——我们担心的异常情况实际上已经发生了。所以需要在运行时处理。

但是,当您说“如果方法中存在 throw 语句,则应始终出现方法签名中的抛出”时,您并不完全正确。这通常是正确的,但并非总是如此。我还可以调用另一个在我的方法中抛出异常的方法,如果我没有捕获它,我的方法需要抛出它。在那种情况下,我没有明确抛出相同的异常。

最后一点是,您只需要在异常是已检查异常时在throws中声明一个异常——这意味着它来自 RuntimeException 的 Exception 类层次结构的另一端。常见的检查异常是 IOException 和 SQLException。如果您不自己处理检查的异常,则必须在方法签名的 throws 部分中列出它们。任何继承 RuntimeException 的子类(例如示例中的 NoSuchElementException 以及讨厌的 NullPointerException)都是未经检查的异常,不必捕获或抛出任何东西。

通常,您对可恢复的问题使用检查异常(客户端知道会发生什么并且可以优雅地处理问题并继续前进)和对灾难性问题(例如无法连接到数据库)使用未经检查的异常。

如果你能通过所有 AOP 的东西,是一个关于如何有效使用检查和未检查异常的精彩讨论。

于 2013-10-05T04:07:14.710 回答
6

Vidya 为您的问题提供了很好的答案。

最重要的一句话是“最后一点是你只需要在异常是检查异常时在 throws 中声明一个异常”

只是为了向您展示示例代码这是什么意思。想象一下,我们想使用 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{} 语句中发生了一些不好的事情 - 它会导致错误。

于 2015-02-07T14:09:52.957 回答
3

throw方法签名中的属性,就像您正确猜到的那样,是对编译器的提示,即该方法引发了一个必须由调用者捕获的异常。这种异常,即所谓的检查异常是调用者必须总是捕获或再次分派给它的调用者的东西。这是编译器级别的东西,签名指定方法能够抛出哪个异常:这try-catch会在调用者中强制执行 or 重新调度,并在方法内部的某处使用 throw 语句,这是开发人员放置的约束以指定有关方法行为。

另一方面,其他异常,即未经检查运行时异常,(NoSucheElementException是一个示例)是您不必指定的异常,因为它们来自不同的情况。

概念上的区别在于,已检查异常通常用于警告应由开发人员以某种方式(考虑IOException)处理的异常情况,而未检查是真正的错误(如NullPointerException您的示例中的或类似NoSuchElementException

于 2013-10-05T03:14:28.987 回答
2

RuntimeExceptions 不必在 try-catch 块中处理,因此不必将它们声明为已抛出,NoSuchElementExceptionRuntimeException因为它扩展了它。

于 2013-10-05T03:12:08.630 回答