为什么重写方法不能抛出比重写方法更广泛的异常?

我正在浏览 KatheSierra 的 SCJP 6书籍,偶然发现了用重写方法抛出异常的解释。我完全不明白。有人能给我解释一下吗?

重写方法不能引发新的已检查异常 或者比重写方法声明的值更大 声明 FileNotFoundException 的 声明 SQLException、 Exception 或任何其他非运行时的 除非它是 FileNotFoundException 的子类。

111157 次浏览

这意味着如果一个方法声明引发给定的异常,子类中的重写方法只能声明引发该异常或其子类。例如:

class A {
public void foo() throws IOException {..}
}


class B extends A {
@Override
public void foo() throws SocketException {..} // allowed


@Override
public void foo() throws SQLException {..} // NOT allowed
}

但是 SQLException没有。

这是因为多态性:

A a = new B();
try {
a.foo();
} catch (IOException ex) {
// forced to catch this by the compiler
}

If B had decided to throw SQLException, then the compiler could not force you to catch it, because you are referring to the instance of B by its superclass - A. On the other hand, any subclass of IOException will be handled by clauses (catch or throws) that handle IOException

您需要能够通过对象的超类引用对象的规则是 Liskov代换原则。

由于未检查的异常可以抛出到任何地方,因此它们不受此规则的约束。如果愿意,您可以将未检查的异常作为文档的一种形式添加到 throw 子句,但编译器不强制任何有关它的内容。

爪哇狼。异常扩展 java.lang。可以扔。Java.io.FileNotFoundException 扩展 java.lang。例外。所以如果一个方法抛出 java.io。然后,在覆盖方法中,您不能抛出比 FileNotFoundException 更高的层次结构,例如,您不能抛出 java.lang。例外。但是,您可以抛出 FileNotFoundException 的子类。但是,您将被迫在重写的方法中处理 FileNotFoundException。编写一些代码并尝试一下!

规则就在那里,所以你不会因为扩大特异性而丢失原来的抛出声明,因为多态性意味着你可以在超类上调用重写的方法。

假设你有一个超类 A,它的方法 M1在 E1中,类 B 从 A 派生,方法 M2覆盖了 M1。M2不能抛出任何与 E1不同或不如 E1专业的东西。

由于多态性,使用类 A 的客户机应该能够将 B 视为 A。如果处理类 A 的代码正在处理异常 E1,因为 M1声明它抛出了这个检查过的异常,但是随后抛出了不同类型的异常,该怎么办?如果 M1抛出 IOException,那么 M2很可能抛出 FileNotFoundException,实际上就是一个 IOException。A 的客户可以毫无问题地处理这个问题。如果抛出的异常范围更广,A 的客户将不会有机会知道这一点,因此也不会有机会捕捉到它。

为了说明这一点,考虑一下:

public interface FileOperation {
void perform(File file) throws FileNotFoundException;
}


public class OpenOnly implements FileOperation {
void perform(File file) throws FileNotFoundException {
FileReader r = new FileReader(file);
}
}

Suppose you then write:

public class OpenClose implements FileOperation {
void perform(File file) throws FileNotFoundException {
FileReader r = new FileReader(file);
r.close();
}
}

这将给出一个编译错误,因为 r.close ()抛出一个 IOException,它比 FileNotFoundException 更广泛。

为了解决这个问题,如果你写道:

public class OpenClose implements FileOperation {
void perform(File file) throws IOException {
FileReader r = new FileReader(file);
r.close();
}
}

您将得到一个不同的编译错误,因为您正在实现执行(...)操作,但是抛出了接口定义的方法中没有包含的异常。

Why is this important? Well a consumer of the interface may have:

FileOperation op = ...;
try {
op.perform(file);
}
catch (FileNotFoundException x) {
log(...);
}

如果允许引发 IOException,则客户端代码不再正确。

注意,如果使用未检查异常,可以避免此类问题。(我不是建议你做或不做,这是一个哲学问题)

重写方法可以引发任何未检查的(运行时)异常,而不管 overridden method declares the exception

例如:

class Super {
public void test() {
System.out.println("Super.test()");
}
}


class Sub extends Super {
@Override
public void test() throws IndexOutOfBoundsException {
// Method can throw any Unchecked Exception
System.out.println("Sub.test()");
}
}


class Sub2 extends Sub {
@Override
public void test() throws ArrayIndexOutOfBoundsException {
// Any Unchecked Exception
System.out.println("Sub2.test()");
}
}


class Sub3 extends Sub2 {
@Override
public void test() {
// Any Unchecked Exception or no exception
System.out.println("Sub3.test()");
}
}


class Sub4 extends Sub2 {
@Override
public void test() throws AssertionError {
// Unchecked Exception IS-A RuntimeException or IS-A Error
System.out.println("Sub4.test()");
}
}

重写方法不能引发新的或比那些更广泛的已检查异常 由重写的方法声明。

例如:

class Super {
public void throwCheckedExceptionMethod() throws IOException {
FileReader r = new FileReader(new File("aFile.txt"));
r.close();
}
}


class Sub extends Super {
@Override
public void throwCheckedExceptionMethod() throws FileNotFoundException {
// FileNotFoundException extends IOException
FileReader r = new FileReader(new File("afile.txt"));
try {
// close() method throws IOException (that is unhandled)
r.close();
} catch (IOException e) {
}
}
}


class Sub2 extends Sub {
@Override
public void throwCheckedExceptionMethod() {
// Overriding method can throw no exception
}
}

我们认为下面的解释是什么

class BaseClass {


public  void print() {
System.out.println("In Parent Class , Print Method");
}


public static void display() {
System.out.println("In Parent Class, Display Method");
}


}




class DerivedClass extends BaseClass {


public  void print() throws Exception {
System.out.println("In Derived Class, Print Method");
}


public static void display() {
System.out.println("In Derived Class, Display Method");
}
}

Java 在 print 方法引发 Exception 时引发编译时异常,基类的 print ()方法不引发任何异常

我能够将其归因于 Exception 比 RuntimeException 窄,它可以是 No Exception (Runtime error)、 RuntimeException 及其子异常

重写方法不能引发比重写方法声明的异常更新或更广泛的已检查异常。

这仅仅意味着 当重写现有方法时,此重载方法引发的异常应该与原始方法引发的异常相同,或者是它的任何子类

注意,检查是否所有检查的异常都在编译时处理,而不是在运行时处理。因此,在编译时,Java 编译器检查重写方法抛出的异常的类型。由于只能在运行时决定执行哪个重写的方法,因此我们无法知道要捕获哪种类型的异常。


例子

假设我们有类 A和它的子类 BA有方法 m1,类 B覆盖了这个方法(让我们称之为 m2,以避免混淆。.).现在假设 m1抛出 E1,而 m2抛出 E2,这是 E1的超类。现在我们编写以下代码:

A myAObj = new B();
myAObj.m1();

请注意,m1只是对 m2的调用(同样,重载方法中的方法签名是相同的,因此不要与 m1m2混淆。.它们只是在这个例子中进行区分... 它们都有相同的签名)。 但是在编译时,Java 编译器所做的就是转到引用类型(在本例中是类 A) ,检查方法是否存在,并期望程序员处理它。所以很明显,你会抛出或接住 E1。现在,在运行时,如果重载的方法抛出 E2,这是 E1的超类,那么... 好吧,这是非常错误的(出于同样的原因,我们不能说 B myBObj = new A())。因此,Java 不允许这样做。重载方法引发的未检查异常必须相同、子类或不存在。

在我看来,这是 Java 语法设计中的一个失败。多态性不应该限制异常处理的使用。事实上,其他计算机语言不这样做(C #)。

此外,方法在更专门的子类中被重写,因此它更复杂,因此更有可能抛出新的异常。

我在这里给出了这个老问题的答案,因为没有答案告诉我们 覆盖方法不能抛出任何东西这个重写方法可以抛出什么:

1)抛出相同的异常

public static class A
{
public void m1()
throws IOException
{
System.out.println("A m1");
}


}


public static class B
extends A
{
@Override
public void m1()
throws IOException
{
System.out.println("B m1");
}
}

2)抛出重写方法的抛出异常的子类

public static class A
{
public void m2()
throws Exception
{
System.out.println("A m2");
}


}


public static class B
extends A
{
@Override
public void m2()
throws IOException
{
System.out.println("B m2");
}
}

3)什么都不要扔。

public static class A
{
public void m3()
throws IOException
{
System.out.println("A m3");
}
}


public static class B
extends A
{
@Override
public void m3()
//throws NOTHING
{
System.out.println("B m3");
}
}

4) Having RuntimeExceptions in throws is not required.

抛出或不抛出可能有 RuntimeException,编译器不会抱怨它。 RuntimeException 不会检查异常

为了理解这一点,让我们考虑一个例子,其中我们有一个类 Mammal,它定义了 readAndGet方法,这个方法读取一些文件,对它进行一些操作,并返回类 Mammal的一个实例。

class Mammal {
public Mammal readAndGet() throws IOException {//read file and return Mammal`s object}
}

Human扩展类 Mammal并重写 readAndGet方法以返回 Human的实例而不是 Mammal的实例。

class Human extends Mammal {
@Override
public Human readAndGet() throws FileNotFoundException {//read file and return Human object}
}

要调用 readAndGet,我们需要处理 IOException,因为它是一个已检查的异常,而哺乳动物的 readAndMethod正在抛出它。

Mammal mammal = new Human();
try {
Mammal obj = mammal.readAndGet();
} catch (IOException ex) {..}

我们知道对于编译器 mammal.readAndGet()是从类 Mammal的对象调用的,但是在运行时 JVM 将解析 mammal.readAndGet()方法调用到类 Human的调用,因为 mammal持有 new Human()

Method readAndMethod from Mammal is throwing IOException and because it is a checked exception compiler will force us to catch it whenever we call readAndGet on mammal

现在假设 Human中的 readAndGet抛出任何其他已检查的异常,例如 Exception,我们知道 readAndGet将从 Human的实例中被调用,因为 mammal持有 new Human()

因为对于编译器来说,方法是从 Mammal调用的,所以编译器会强制我们只处理 IOException,但是在运行时,我们知道方法会抛出 Exception异常,这是没有得到处理的,如果方法抛出异常,我们的代码会中断。

That's why it is prevented at the compiler level itself and we are not allowed to throw any new or broader checked exception because it will not be handled by JVM at the end.

还有其他规则,我们需要遵循,而覆盖的方法,你可以阅读更多的 为什么我们应该遵循方法重写规则了解原因。

让我们来做一个采访问题。 在超类中有一个抛出 NullPointerException 的方法 抛出 RuntimeException 的方法?

为了回答这个问题,让我们知道什么是 Uncheck 和 Checked 异常。

  1. 中描述的检查异常必须显式捕获或传播 基本的 try-catch-finally 异常处理。未检查的异常没有这个要求。 They don't have to be caught or declared thrown.

  2. 在 Java 中检查异常扩展 Java.lang.Exception 类。

公共类 NullPointerException 扩展 RuntimeException

未检查异常扩展 java.lang.RuntimeException。 这就是为什么 NullPointerException 是 Uncheked 异常的原因。

让我们举个例子: 例子一:

    public class Parent {
public void name()  throws NullPointerException {
System.out.println(" this is parent");
}
}


public class Child  extends Parent{
public  void name() throws RuntimeException{
System.out.println(" child ");
}


public static void main(String[] args) {
Parent parent  = new Child();
parent.name();// output => child
}
}

The program will compile successfully. 例二:

    public class Parent {
public void name()  throws RuntimeException {
System.out.println(" this is parent");
}
}


public class Child  extends Parent{
public  void name() throws  NullPointerException {
System.out.println(" child ");
}


public static void main(String[] args) {
Parent parent  = new Child();
parent.name();// output => child
}
}

该程序也将成功编译。 因此,很明显,在未检查异常的情况下不会发生任何事情。 现在,让我们看看在检查异常情况下会发生什么。 例三: 当基类和子类都引发检查异常时

    public class Parent {
public void name()  throws IOException {
System.out.println(" this is parent");
}
}
public class Child  extends Parent{
public  void name() throws IOException{
System.out.println(" child ");
}


public static void main(String[] args) {
Parent parent  = new Child();


try {
parent.name();// output=> child
}catch( Exception e) {
System.out.println(e);
}


}
}

程序将成功编译。 例子四: When child class method is throwing border checked exception compared to the same method of base class.

import java.io.IOException;


public class Parent {
public void name()  throws IOException {
System.out.println(" this is parent");
}
}
public class Child  extends Parent{
public  void name() throws Exception{ // broader exception
System.out.println(" child ");
}


public static void main(String[] args) {
Parent parent  = new Child();


try {
parent.name();//output=> Compilation failure
}catch( Exception e) {
System.out.println(e);
}


}
}

该程序将无法编译。因此,我们必须小心,当我们使用检查异常。

子类的重写方法只能抛出作为超类方法的检查异常的子类的多个检查异常,但不能抛出与超类方法的检查异常无关的多个检查异常

Java 是 giving you the choice来限制父类中的异常,因为它假设 客户会限制捕获的内容。恕我直言,你本质上应该 永远不会使用这个“功能”,因为你的客户可能需要灵活性的道路上。

Java 是一种设计很差的古老语言,现代语言没有这样的限制。 解决这个缺陷的最简单的方法是让你的基类 throw Exception总是。客户端可以抛出更具体的异常,但是要使基类非常宽泛。

Rule of handling check and unchecked exceptions on overridden methods

-When parent-class method declares no exception, then child-class overriding- 方法可以声明

 1. No exception or
2. Any number of unchecked exception
3. but strictly no checked exception

- 当父类方法声明未检查异常时,子类重写方法可以声明 ,

 1. No exception or
2. Any number of unchecked exception
3. but strictly no checked exception

当父类方法声明选中的异常时,子类重写方法可以声明,

 1. No exception or
2. Same checked exception or
3. Sub-type of checked exception or
4. any number of unchecked exception

即使在父类的方法中同时声明了检查异常和未检查异常的组合,上述结论仍然成立

Ref

Overriding method can (yet, it does 没有 have to) throw:

  1. any 不受约束,

    还有

  2. only those 检查过了 exceptions that are covariant types (same or extender) of whatever overridden method throws.

请考虑以下三个陈述

  1. 方法必须更改参数列表 重载方法可能会改变返回类型 重载方法可以声明更广泛的检查异常 选择那些真实的。