Java 重写 throws 子句

Java overriding throws clause

我正在通过阅读 S G Ganesh 和 Tushar Sharma 编写的书来学习 OCJP 考试。在第 346 页有一段文字说:

What if you try changing the throws clause? There are many ways to change the throws clause in the overriding method, including the following:
a. Not providing any throws clause.
b. Listing more general checked exceptions to throw.
c. Listing more checked exceptions in addition to the given checked exception(s) in the base method.
If you attempt any of these three cases, you’ll get a compiler error. For example, try not providing the throws clause in the readIntFromFile() method in the class that implements the IntReader interface.

public int readIntFromFile() {
    Scanner consoleScanner = new Scanner(new File("integer.txt"));
    return consoleScanner.nextInt();
}

You’ll get this compiler error: “unreported exception FileNotFoundException; must be caught or declared to be thrown.”
To summarize, the base class method’s throws clause is a contract that it provides to the caller of that method:
it says that the caller should handle the listed exceptions or declare those exceptions in its throws clause. When overriding the base method, the derived method should also adhere to that contract. The caller of the base method is prepared to handle only the exceptions listed in the base method, so the overriding method cannot throw more general or other than the listed checked exceptions.
However, note that this discussion that the derived class method’s throws clause should follow the contract for the base method’s throws clause is limited to checked exceptions. Unchecked exceptions can still be added or removed from the contract when compared to the base class method’s throws clause. For example, consider the following:

public int readIntFromFile() throws IOException, NoSuchElementException {
    Scanner consoleScanner = new Scanner(new File("integer.txt"));
    return consoleScanner.nextInt();
}

This is an acceptable throws clause since NoSuchElementException can get thrown from the readIntFromFile() method. This exception is an unchecked exception, and it gets thrown when the nextInt() method could not read an integer from the file. This is a common situation, for example, if you have an empty file named integer.txt; an attempt to read an integer from this file will result in this exception.

我有点担心"a."。它说,如果您不提供任何 throws 子句,代码将无法编译。但是当我学习 OCA 时,我记得我读到你可以提供相同的 throws 子句,一个更具体的异常或根本没有 throws 子句,并且代码仍然可以编译并且这些仅适用于检查异常。 我尝试做一些测试,但我无法得到“未报告的异常 FileNotFoundException;必须被抓住或 宣布被抛出。”。我记得我看过,但我不知道是在什么条件下。

嗯,书错了:

class A {
    public void foo() throws FileNotFoundException {

    }
}

class B extends A {
    public void foo() {

    }
}

编译成功。对于此类语言功能,最好参考 Java 语言规范。

正如Manouti所说,这本书是错误的。你绝对可以有更精确的或没有例外,但你不能有更一般的:

interface A {
    void meth() throws IOException;
}

class B implements A {
    @Override
    void meth() throws FileNotFoundException { } // compiles fine
}

class C implements A  {
    @Override
    void meth() { } // compiles fine
}

class D implements A  {
    @Override
    void meth() throws Exception { } // compile error
}

这本书混淆了两件事。它是在谈论重写子类中的 throws 子句,但根据示例,它与此完全无关:

public int readIntFromFile() {
    Scanner consoleScanner = new Scanner(new File("integer.txt"));
    return consoleScanner.nextInt();
} 

你会在这里得到一个编译错误,因为 Scanner 构造函数抛出一个 FileNotFoundException,它没有在 readIntFromFile() 中被捕获或重新声明。也就是从readIntFromFile()的声明中看不出来可能会抛出异常

这适用于subclass/base 类 仅当子类中的方法调用其超方法时:

class A {
    void meth() throws IOException {
        // ...
    }
}

class B extends A {
    @Override
    public
    void meth() { 
        super.meth(); // compile error here
        // ...
    }
}

反例点A

书上A点错了

以下代码可以完美编译:

class A {
    void method() throws GeneralSecurityException {
    }
}

class B extends A {
    void method() {
    }
}

Class B 显然使 A 点无效。

反例点C

这本书也过于简化了 C 点:只要抛出的异常是已抛出的已检查异常的子class,代码就是正确的。

class C extends A {
    void method() throws NoSuchAlgorithmException, InvalidKeyException {
    }
}

在代码中 NoSuchAlgorithmExceptionInvalidKeyExceptionGeneralSecurityException 的子class。

Class C 显示点 C 的过度简化。

JLS 参考资料

以下是The Java Language Specification, Java SE 8 Edition, chapter 11.2中的状态:

...

The checked exception classes (§11.1.1) named in the throws clause are part of the contract between the implementor and user of the method or constructor. The throws clause of an overriding method may not specify that this method will result in throwing any checked exception which the overridden method is not permitted, by its throws clause, to throw (§8.4.8.3).

...

8.4.8.3 中的参考内容如下:

...

  • If m<sub>2</sub> has a throws clause that mentions any checked exception types, then m<sub>1</sub> must have a throws clause, or a compile-time error occurs.
  • For every checked exception type listed in the throws clause of m<sub>2</sub>, that same exception class or one of its supertypes must occur in the erasure (§4.6) of the throws clause of m<sub>1</sub>; otherwise, a compile-time error occurs.

...

JLS 文本的讨论

这些 JLS 章节很有意义。最后,调用声明特定已检查异常的方法的代码将具有处理该特定已检查异常的代码。如果子 class 实例的方法抛出另一个已检查异常,则不太可能处理已检查异常。另一方面,如果方法 没有 抛出调用者期望的任何异常,则调用者是可以的;异常处理代码将不会被调用。

代码应该总是期望接收运行时异常,因此基于 RuntimeException 的异常免于编译时检查。

经验法则

只要您只抛出调用者期望的异常,代码就应该没问题。

java.io.Printwritter/PrintStream :: close() api in java 是 IOException thrown at super class (java.io.OutputStream :: close()) 的最好例子子class.

还有一件事是

try {
   //CheckedExceptionX must be thrown from here
}
catch(CheckedExceptionX e) {
}

但是

public void someAPI throws CheckedExceptionX {
     //trowing CheckedExceptionX is not mandatory in this method
     //Means this body can be free from throwing that mentioned CheckedException 
}