Answers:
这意味着,如果方法声明要抛出给定的异常,则子类中的重写方法只能声明抛出该异常或其子类。例如:
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子句中,但是编译器不会对此执行任何强制措施。
@Override public void foo() {...}是合法的。
覆盖方法可以抛出任何未经检查的(运行时)异常,而不管覆盖方法是否声明了异常
例:
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()");
}
}
我在这里为旧问题提供此答案,因为没有答案可以说明一个事实,那就是覆盖方法不能抛出任何东西:
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不是检查的异常。如果未捕获,则仅需要将已检查的异常显示在抛出中。
为了说明这一点,请考虑:
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,则客户端的代码不再正确。
请注意,如果您使用未经检查的异常,则可以避免此类问题。(我不建议您做或不做,这是一个哲学问题)
让我们接受面试问题。有一个方法会在超类中引发NullPointerException。我们可以使用抛出RuntimeException的方法覆盖它吗?
要回答此问题,请让我们知道什么是“未检查和已检查”异常。
必须按照基本try-catch-finally异常处理中的描述显式捕获或传播检查的异常。未检查的异常没有此要求。它们不必被捕获或声明为抛出。
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异常时我们必须要小心。
假设您拥有方法M1抛出E1的超类A,而类A则继承了方法M2覆盖M1的类B。M2不能抛出与E1不同或更少的东西。
由于具有多态性,使用类A的客户端应该能够将B视为A。如果处理类A的这段代码正在处理异常E1,如M1声明它抛出了此检查的异常,然后又抛出了不同类型的异常,该怎么办?如果M1抛出IOException,则M2很可能抛出FileNotFoundException,因为它是IOException。A的客户可以毫无问题地进行处理。如果引发的异常范围更大,则A的客户将没有机会了解此情况,因此也就没有机会抓住它。
好吧,java.lang.Exception扩展了java.lang.Throwable。java.io.FileNotFoundException扩展了java.lang.Exception。因此,如果方法抛出java.io.FileNotFoundException,则在覆盖方法中,您不能向层次结构中抛出比FileNotFoundException高的任何东西,例如,您不能抛出java.lang.Exception。您可以抛出FileNotFoundException的子类。但是,您将不得不在重写方法中处理FileNotFoundException。敲入一些代码,然后尝试一下!
这里有规则,因此您不会通过扩大特异性而失去原始的throws声明,因为多态性意味着您可以在超类上调用重写的方法。
覆盖方法不得抛出比被覆盖方法声明的异常新的或更广泛的检查异常。
例:
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
}
}
覆盖方法不得抛出比被覆盖方法声明的异常新的或更广泛的检查异常。
这只是意味着当您覆盖现有方法时,此重载方法引发的异常应该与原始方法引发的异常或其任何子类相同。
请注意,检查是否处理了所有检查到的异常是在编译时而不是在运行时完成的。因此,在编译时,Java编译器会检查重写的方法引发的异常类型。由于只能在运行时确定将执行哪个重写的方法,因此我们不知道必须捕获哪种异常。
例
假设我们有class A及其子类B。Ahas方法m1和类B已重写此方法(m2为了避免混淆,请对其进行调用。)现在让我们说m1throws E1和m2throws E2,这是E1的超类。现在,我们编写以下代码:
A myAObj = new B();
myAObj.m1();
请注意,m1这不过是对的调用m2(再次,方法签名在重载方法中是相同的,因此请不要与m1和混淆m2。在此示例中,它们只是为了区分……它们都具有相同的签名)。但是在编译时,所有Java编译器所做的工作就是转到引用类型(A在这种情况下为Class )检查该方法是否存在,并希望程序员对其进行处理。所以很明显,您会抛出或抓住E1。现在,在运行时,如果重载的方法throw E2是E1的超类,那么……好吧,这是非常错误的(出于同样的原因,我们不能说B myBObj = new A())。因此,Java不允许这样做。重载方法抛出的未经检查的异常必须相同,子类相同或不存在。
为了理解这一点,让我们考虑一个示例,其中我们有一个类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,由于它是一个检查异常,因此编译器将在每次调用时强制我们捕获它readAndGet上mammal
现在假设readAndGet在Human被抛出任何其他检查的异常如例外,我们知道readAndGet将得到的实例调用Human,因为mammal是控股new Human()。
因为对于编译器,该方法是从调用的Mammal,所以编译器将迫使我们仅进行处理,IOException但在运行时,我们知道该方法将被抛出Exception引发未得到处理的异常,并且如果该方法引发异常,则代码将中断。
这就是为什么在编译器级别本身不会阻止它的原因,并且我们不允许抛出任何新的或更广泛的检查异常,因为最终JVM不会对其进行处理。
覆盖方法时还需要遵循其他规则,您可以阅读有关为什么我们应该遵循方法覆盖规则的更多信息以了解原因。
我们对以下内容有何解释?
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及其子异常。
处理覆盖方法的检查和未检查异常的规则
- 当父类方法未声明任何异常时,子类覆盖方法可以声明,
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
以上所有结论均成立,即使在父类的方法中声明了已检查和未检查异常的组合也是如此