为什么覆盖方法不能引发比覆盖方法更广泛的异常?


104

我正在浏览Kathe sierra编写的SCJP 6,并遇到了有关以重写方法引发异常的解释。我真的不明白。有人可以向我解释吗?

覆盖方法不得抛出比被覆盖方法声明的异常新的或更广泛的检查异常。例如,声明FileNotFoundException的方法不能被声明SQLException,Exception或任何其他非运行时异常的方法覆盖,除非它是FileNotFoundException的子类。


1
这是一个可能对您有所帮助的网站:javapractices.com/topic/TopicAction.do?Id=129
Tim Bish 2011年

Answers:


155

这意味着,如果方法声明要抛出给定的异常,则子类中的重写方法只能声明抛出该异常或其子类。例如:

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
}

SocketException extends IOException,但SQLException没有。

这是由于多态性:

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

如果B决定抛出SQLException,则编译器无法强迫您捕获它,因为您正在B通过其超类- 引用实例A。另一方面,任何IOException将由处理的子句(catch或throws)处理IOException

您需要能够按其超类引用对象的规则是Liskov替换原理。

由于未经检查的异常可以抛出到任何地方,因此它们不受此规则的约束。如果需要,可以将未经检查的异常作为文档形式添加到throws子句中,但是编译器不会对此执行任何强制措施。


这在实现接口时也适用吗?我不确定实现接口是否仍称为“覆盖”。
Muhammad Gelbana

@Override public void foo(){..}怎么样,我知道这是允许的,但这种情况的解释不清楚。
纳斯卡

4
@danip重写方法可以引发从重写方法引发的异常的任何子集。空集也是一个子集。这就是为什么@Override public void foo() {...}是合法的。
开发人员MariusŽilėnas16年

@Bozho不应该这样,如果某个方法声明要抛出给定的异常,则子类中的重写方法只能声明抛出该异常或其子类,或者根本不声明throws子句
Raman Sahasi

那么在现实世界中如何克服?我需要从已实现的接口中重写一个方法,但是我的实现包括抛出减速度,而接口则没有。这里的标准程序是什么?
美联社

22

覆盖方法可以抛出任何未经检查的(运行时)异常,而不管覆盖方法是否声明了异常

例:

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()");
    }
}

当接口未声明子类会声明的运行时异常时,如何强制错误或警告?我试图出于文档目的强制一致性。检查接口类型的所有异常(已检查和未检查)更容易,而不是先找到接口类型然后深入研究实现以查看它是否抛出IOException或IllegalArgumentException。
anon58192932

14

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

此外,方法在更专业的子类中被重写,因此它更复杂,因此,更可能引发新异常。


8

我在这里为旧问题提供此答案,因为没有答案可以说明一个事实,那就是覆盖方法不能抛出任何东西

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)不需要抛出RuntimeExceptions。

是否抛出RuntimeExceptions,编译器不会抱怨。RuntimeExceptions不是检查的异常。如果未捕获,则仅需要将已检查的异常显示在抛出中。


6

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

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);
  }
}

假设您然后编写:

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

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

为什么这很重要?接口的使用者可能会:

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

如果允许抛出IOException,则客户端的代码不再正确。

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


3

让我们接受面试问题。有一个方法会在超类中引发NullPointerException。我们可以使用抛出RuntimeException的方法覆盖它吗?

要回答此问题,请让我们知道什么是“未检查和已检查”异常。

  1. 必须按照基本try-catch-finally异常处理中的描述显式捕获或传播检查的异常。未检查的异常没有此要求。它们不必被捕获或声明为抛出。

  2. Java中的检查异常扩展了java.lang.Exception类。未经检查的异常扩展了java.lang.RuntimeException。

公共类NullPointerException扩展了RuntimeException

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

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

    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
    }
}

该程序将成功编译。范例2:

    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
    }
}

该程序还将成功编译。因此很明显,在未检查的异常的情况下什么也不会发生。现在,让我们看一下Checked异常的情况。示例3:当基类和子类都抛出一个检查异常

    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);
        }

    }
}

该程序将成功编译。示例4:与相同的基类方法相比,当子类方法引发边界检查的异常时。

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);
        }

    }
}

该程序将无法编译。因此,在使用Checked异常时我们必须要小心。


2

假设您拥有方法M1抛出E1的超类A,而类A则继承了方法M2覆盖M1的类B。M2不能抛出与E1不同或更少的东西。

由于具有多态性,使用类A的客户端应该能够将B视为A。如果处理类A的这段代码正在处理异常E1,如M1声明它抛出了此检查的异常,然后又抛出了不同类型的异常,该怎么办?如果M1抛出IOException,则M2很可能抛出FileNotFoundException,因为它是IOException。A的客户可以毫无问题地进行处理。如果引发的异常范围更大,则A的客户将没有机会了解此情况,因此也就没有机会抓住它。


Perhac ::对于已检查和未检查的异常都适用吗?还是有所不同?
ylnsagar '16

@ylnsagar,这仅用于检查的异常。未检查的异常(RuntimeException的子类型)也可以称为“程序员错误”,通常不应尝试捕获异常,因此不需要在throws子句中声明。未经检查的异常随时可能在任何代码中发生。上面的讨论仅与检查的异常有关
PeterPerháč16年

@Perhac ::是的,您是对的,但是据我的理解,通过阅读文章。对于未检查的异常也是如此。例如,如果超类方法抛出Null指针异常,并且重写该方法的子类抛出异常。这里的Exception是Null Pointer Exception的超类。然后,编译器将不允许这样做。
ylnsagar's

1

好吧,java.lang.Exception扩展了java.lang.Throwable。java.io.FileNotFoundException扩展了java.lang.Exception。因此,如果方法抛出java.io.FileNotFoundException,则在覆盖方法中,您不能向层次结构中抛出比FileNotFoundException高的任何东西,例如,您不能抛出java.lang.Exception。您可以抛出FileNotFoundException的子类。但是,您将不得不在重写方法中处理FileNotFoundException。敲入一些代码,然后尝试一下!

这里有规则,因此您不会通过扩大特异性而失去原始的throws声明,因为多态性意味着您可以在超类上调用重写的方法。


1

覆盖方法不得抛出比被覆盖方法声明的异常新的或更广泛的检查异常。

例:

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
    }
}

1

覆盖方法不得抛出比被覆盖方法声明的异常新的或更广泛的检查异常。

这只是意味着当您覆盖现有方法时,此重载方法引发的异常应该与原始方法引发的异常或其任何子类相同

请注意,检查是否处理了所有检查到的异常是在编译时而不是在运行时完成的。因此,在编译时,Java编译器会检查重写的方法引发的异常类型。由于只能在运行时确定将执行哪个重写的方法,因此我们不知道必须捕获哪种异常。


假设我们有class A及其子类BAhas方法m1和类B已重写此方法(m2为了避免混淆,请对其进行调用。)现在让我们说m1throws E1m2throws E2,这是E1的超类。现在,我们编写以下代码:

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

请注意,m1这不过是对的调用m2(再次,方法签名在重载方法中是相同的,因此请不要与m1和混淆m2。在此示例中,它们只是为了区分……它们都具有相同的签名)。但是在编译时,所有Java编译器所做的工作就是转到引用类型(A在这种情况下为Class )检查该方法是否存在,并希望程序员对其进行处理。所以很明显,您会抛出或抓住E1。现在,在运行时,如果重载的方法throw E2E1的超类,那么……好吧,这是非常错误的(出于同样的原因,我们不能说B myBObj = new A())。因此,Java不允许这样做。重载方法抛出的未经检查的异常必须相同,子类相同或不存在。


类Parent {无效method()抛出IndexOutOfBoundsException {System.out.println(“ Parent method”); }}类Child扩展Parent {void method()抛出RuntimeException {System.out.println(“ Child method”); }如果父类抛出运行时异常的子级,而子级本身抛出运行时异常。有效吗
abhiagNitk '17

1

为了理解这一点,让我们考虑一个示例,其中我们有一个类Mammal,该类定义了readAndGet一种方法,该方法正在读取某些文件,对该文件执行一些操作并返回class的实例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是holding new Human()

readAndMethodfrom Mammal引发的方法IOException,由于它是一个检查异常,因此编译器将在每次调用时强制我们捕获它readAndGetmammal

现在假设readAndGetHuman被抛出任何其他检查的异常如例外,我们知道readAndGet将得到的实例调用Human,因为mammal是控股new Human()

因为对于编译器,该方法是从调用的Mammal,所以编译器将迫使我们仅进行处理,IOException但在运行时,我们知道该方法将被抛出Exception引发未得到处理的异常,并且如果该方法引发异常,则代码将中断。

这就是为什么在编译器级别本身不会阻止它的原因,并且我们不允许抛出任何新的或更广泛的检查异常,因为最终JVM不会对其进行处理。

覆盖方法时还需要遵循其他规则,您可以阅读有关为什么我们应该遵循方法覆盖规则的更多信息以了解原因。


0

我们对以下内容有何解释?

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");
    }
}

当print方法抛出Exception时,类DerivedClass.java抛出编译时异常,baseclass的print()方法不抛出任何异常

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


0

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


0

Java 允许您选择限制父类中的异常,因为它假设客户端将限制catch的内容。恕我直言,你基本上应该使用此“功能”,因为您的客户可能需要长期的灵活性。

Java是设计较差的旧语言。现代语言没有这种限制。解决此缺陷的最简单方法是throw Exception始终使您的基类成为可能。客户可以抛出更具体的异常,但会使您的基类真正变得广泛。


0

处理覆盖方法的检查和未检查异常的规则

- 当父类方法未声明任何异常时,子类覆盖方法可以声明

 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

以上所有结论均成立,即使在父类的方法中声明了已检查和未检查异常的组合也是如此

参考

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.