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

时间:2011-05-03 20:38:13

标签: java

我正在阅读Kathe sierra撰写的SCJP 6书,并且发现了在重写方法中抛出异常的解释。我完全没有得到它。任何人都可以向我解释一下吗?

  

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

16 个答案:

答案 0 :(得分:143)

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

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的实例 - {{1 }}。另一方面,A的任何子类将由处理IOException的子句(catch或throws)处理

您需要能够通过超类引用对象的规则是Liskov Substitution Principle.

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

答案 1 :(得分:21)

重写方法可以抛出任何未经检查的(运行时)异常,无论是否 重写方法声明异常

示例:

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

答案 2 :(得分:13)

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

此外,一个方法在一个更特殊的子类中被覆盖,因此它更复杂,因此更有可能抛出新的异常。

答案 3 :(得分:6)

我在这里提供了这个问题的答案,因为没有答案说明了覆盖方法什么都不会抛出任何东西这里再次提出了重写方法:

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)抛出overriden方法的抛出异常的子类

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)不需要在throws中使用RuntimeExceptions。

抛出或不抛出RuntimeExceptions,编译器不会抱怨它。 RuntimeExceptions不是已检查的异常。 如果未捕获,则只有已检查的异常才会显示在抛出中。

答案 4 :(得分:5)

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

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,则客户端的代码不再正确。

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

答案 5 :(得分:3)

让我们采访一个问题。 有一种方法会在超类中抛出NullPointerException。我们可以用一个覆盖它 抛出RuntimeException的方法?

要回答这个问题,请告诉我们什么是未经检查和已检查的例外。

  1. 必须按照中所述明确捕获或传播已检查的异常 基本的try-catch-finally异常处理。未经检查的异常没有此要求。 他们不必被抓或被抛出。

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

  3. public class NullPointerException扩展RuntimeException

    未经检查的异常会扩展java.lang.RuntimeException。 这就是Nul​​lPointerException是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例外时,我们必须要小心。

答案 6 :(得分:2)

假设你有超级A,方法M1 throwin E1和B级,来自A,方法M2覆盖M1。 M2不能抛出与E1不同或不太专业的东西。

由于多态性,使用A类的客户端应该能够像对待A一样对待B。Inharitance ===>是-a(B是-A A)。如果处理类A的代码处理异常E1会怎么样,因为M1声明它抛出了这个检查过的异常,但是抛出了不同类型的异常?如果M1抛出IOException M2可能会抛出FileNotFoundException,因为它是一个IOException。 A的客户可以毫无问题地处理这个问题。如果抛出的异常范围更广,A的客户就没有机会了解这一点,因此没有机会抓住它。

答案 7 :(得分:1)

java.lang.Exception扩展了java.lang.Throwable。 java.io.FileNotFoundException扩展了java.lang.Exception。因此,如果一个方法抛出java.io.FileNotFoundException然后在override方法中,你不能抛出比FileNotFoundException更高的层次结构,例如你不能抛出java.lang.Exception。您可以抛出FileNotFoundException的子类。但是,您将被迫在overriden方法中处理FileNotFoundException。敲了一些代码并试一试!

规则就在那里,所以你不要通过扩大特异性来丢失原始的throws声明,因为多态意味着你可以在超类上调用overriden方法。

答案 8 :(得分: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
    }
}

答案 9 :(得分:1)

重写方法不得抛出新的或更广泛的已检查异常,而不是被重写方法声明的异常。

这意味着当您覆盖现有方法时,此重载方法抛出的异常应该是原始方法抛出的异常或其任何子类

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


示例

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

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

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

答案 10 :(得分:1)

为了理解这一点,我们考虑一个示例,其中我们有一个类Mammal,它定义了readAndGet方法,该方法正在读取某个文件,对其执行某些操作并返回类的实例{ {1}}。

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()的对象调用的,但是,运行时JVM将解析Mammal方法调用来自类{{1}的调用因为mammal.readAndGet()持有Human

来自mammal的方法new Human()正在抛出readAndMethod,因为它是一个经过检查的异常,只要我们在Mammal上调用IOException,就会强制我们抓住它

现在假设readAndGet中的mammal投掷任何其他已检查的例外,例如例外,我们知道readAndGet会从Human的实例调用,因为readAndGet持有Human

因为编译器的方法是从mammal调用的,所以编译器会强制我们只处理new Human()但是在运行时我们知道方法会抛出Mammal异常而不是IOException处理完毕后,如果方法抛出异常,我们的代码就会中断。

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

在覆盖方法时我们还需要遵循其他规则,您可以在Why We Should Follow Method Overriding Rules上阅读更多内容以了解原因。

答案 11 :(得分: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");
    }
}

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

我能够将此归因于Exception比RuntimeException更窄的事实,它可以是No Exception(运行时错误),RuntimeException及其子异常

答案 12 :(得分:0)

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

答案 13 :(得分:0)

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

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

答案 14 :(得分: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

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

Ref

答案 15 :(得分:0)

覆盖方法可以(但它不是必须)抛出:

  1. 任何未选中,

  2. 只有那些检查异常是协变类型(相同或扩展)的任何重写方法抛出的异常。