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

arp*_*oid 95 java

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

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

Boz*_*zho 150

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

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
}
Run Code Online (Sandbox Code Playgroud)

SocketException extends IOException,但SQLException没有.

这是因为多态性:

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

如果B已经决定抛出SQLException,那么编译器就不能强迫你捕获它,因为你指的B是它的超类的实例- A.另一方面,任何子类都IOException将由句柄(catch或throws)处理IOException

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

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

  • @danip重写方法可以抛出从overriden方法抛出的任何异常子集.空集也是一个子集.这就是为什么`@Override public void foo(){...}`是合法的. (3认同)
  • @Bozho不应该是_如果一个方法声明抛出一个给定的异常,子类中的重写方法只能声明抛出该异常或其子类,或者**声明根本不抛出异常**_ (2认同)

小智 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()");
    }
}
Run Code Online (Sandbox Code Playgroud)


cal*_*ari 14

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

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


dis*_*ame 7

我在这里提供这个问题的答案,因为没有答案告诉我们这样一个事实,即重写方法可以再次抛出什么,重写方法可以抛出:

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

4)不需要在throws中使用RuntimeExceptions.

抛出或不抛出RuntimeExceptions,编译器不会抱怨它.RuntimeExceptions不是已检查的异常.如果没有捕获,则只有已检查的异常才会出现在throws中.


Dil*_*nga 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);
  }
}
Run Code Online (Sandbox Code Playgroud)

假设你然后写:

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

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

要解决这个问题,如果你写:

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

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

为什么这很重要?那么界面的消费者可能有:

FileOperation op = ...;
try {
  op.perform(file);
}
catch (FileNotFoundException x) {
  log(...);
}
Run Code Online (Sandbox Code Playgroud)

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

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