bugstac...

Java异常系列第三集——多态中的异常

2017-12-25  本文已影响53人  will4it

本博客为个人原创,转载需在明显位置注明出处

多态在实际项目开发中是经常用到的一个概念,那么多态中的异常是怎样的呢?下面我们就来讨论一下,为了节省时间,我直接用了《Thinking in Java》中的栗子(由于是多态,肯定有一系列的继承关系,所以代码较多,请仔细阅读):

class BaseballException extends Exception {
}

class Foul extends BaseballException {
}

class Strike extends BaseballException {
}

首先是三个自定义异常类,Foul和Strike均是BaseballException的子类

abstract class Inning {

    public Inning() throws BaseballException {
    }
    
    public Inning(String s) {
    }

    public void event() throws BaseballException {
    }

    public void walk() {
    }

    public abstract void atBat() throws Strike, Foul;

}

接下来是一个抽象类,其中atBat()是抽象方法,注意每个方法抛出的异常,下面会有逐个总结和解释

class StormException extends Exception {
}

class RainedOut extends StormException {
}

class PopFoul extends Foul {
}

又是三个自定义异常,注意,RainedOut是刚刚定义的StormException的子类,PopFoul是上面定义的Foul的子类

interface Storm {
    public void event() throws RainedOut;

    public void rainHard() throws RainedOut;
}

擦,还有一个接口,感觉整个人都不好了,在坚持一下,马上就到最后了。注意一下,接口里面有一个event()方法,上面抽象类Inning也有一个event()方法,只是抛出的异常不同

class StormyInning extends Inning implements Storm {

    public StormyInning() throws RainedOut, BaseballException {
        // 构造方法必须throws父类构造方法的Exception
        // 子类构造方法调用super,不可捕获父类的异常
    }

    public StormyInning(String s) {
        super("");
    }

    @Override
    public void event() {
        // 父类event方法和接口event方法抛出的异常不一致,所以只能重写方法,不抛出任何异常
    }

    @Override
    public void rainHard() {
        // 子类重写方法,可以将抛出的异常范围缩小或者不抛出异常
    }

    @Override
    public void atBat() throws PopFoul {
        // 子类重写方法,可以抛出父类抛出异常的子类异常
        // PopFoul extends Foul
    }

    // @Override
    // public void walk() throws StormException {
        // 如果父类方法未抛出异常,子类重写方法不可抛出异常
    // }
}

终于结束了,最后一个实现类StormInning,集成抽象类Inning,实现接口Storm,内部重写了一些方法,下面我们就挨个总结:

重写方法###

1.父类方法不throws异常,子类重写方法不可throws

看代码中的walk方法,父类Inning中walk方法是没有抛出异常的,但是在子类StormInning中重写并且添加异常说明,编译不通过的。回顾一下多态的概念,简单来说就是父类引用指向子类对象,试想一下,父类方法中没有抛出异常,子类却抛出了异常,假设可以编译通过,那么父类的引用去调用这个方法时,是不需要捕获异常的,但是真正执行的是子类中重写的方法,只要子类方法中出现问题抛出异常,结果都是致命的

2.子类重写方法可以缩小异常范围或不throws异常

这一点跟1恰恰相反,看rainedOut方法,在接口中添加了异常说明,但是重写时去掉了异常说明。这个是可行的,因为无论怎样,父类引用在调用rainedOut方法时都会提示要捕获异常,子类重写方法中不抛异常没问题,抛了异常也可以捕获到。

3.子类重写方法可throws父类异常的子类

看下代码中的atBat方法,父类Inning中定义抛出的是Foul异常,子类重写时抛出了Foul的子类PopFoul异常,这个也是ok的。关于这点可以参考上一篇文章中的异常匹配部分,捕获异常的时候,父类异常是可以匹配子类异常的。

4.父类和接口包含同一方法,但定义抛出的异常不同,子类只能重写方法并且不可throws任何异常

Inning类和Storm接口都有一个相同的event方法,区别只是抛出的异常类型不同,一个抛出BaseballException异常,另一个是RainedOut异常,我们在子类StormInning中重写该方法时,若只抛出BaseballException,那么不匹配接口中的定义,若只抛出RainedOut,就不匹配父类中的定义,两个异常都抛出,父类和接口都不匹配,真是里外都不是人。所以这种情况下,重写方法时不能抛出任何异常,正好符合上述总结2。

构造方法###

1.子类构造方法可以扩大异常范围

与重写方法只能缩小异常范围相反,子类构造方法是可以扩大异常范围的,为什么?《Thinking in java》中只有结论没有做任何解释,说说我的理解:还是多态的概念,父类引用在指向具体的对象时,需要new出具体的类对象,子类对象和父类对象是不一样的,而在调用方法时父类与子类就没有区分,方法名都是一致的,只是在运行时执行的地方不一样。那么子类构造方法中扩大异常范围,增加一些其他异常的抛出,完全不会影响调用方的异常捕获。可以参考代码中StormInning类的无参构造方法,比父类多抛出了RainedOut异常

2.如果子类构造方法调用了父类的有异常抛出的构造方法,必须throws父类构造方法的异常,该异常不可再子类中捕获

还是StormInning的无参构造方法,若是删除BaseballException的异常说明,编译器就会报错,为什么?因为虽然没有显示调用,但是StormInning的无参构造方法实际上调用了父类的默认构造方法,而父类Inning的默认构造方法有异常抛出,所以子类必须抛出这个异常。或许你会说,我完全可以在子类构造方法中调用一下super(),然后try-catch,这样子类就不需要抛出异常了。一开始我也以为可以,实际上却不行,因为我们是无法在子类捕获父类构造方法抛出的异常的,必须抛出,至于为什么,我也不是太清楚,有知道的盆友还请不吝赐教。

3.父类引用和子类引用在调用方法时,捕获的异常可能不同

这有个前提条件就是父类方法和子类方法抛出的异常要不同才行,就拿atBat方法为例:

@Test
public void testPolymophism() {
    try {
        StormyInning sinn = new StormyInning();
        sinn.atBat();
    } catch (PopFoul e) {
        e.printStackTrace();
    } catch (RainedOut e) {
        e.printStackTrace();
    } catch (BaseballException e) {
        e.printStackTrace();
    }

    try {
        Inning inn = new StormyInning();
        inn.atBat();
    } catch (Strike e) {
        e.printStackTrace();
    } catch (Foul e) {
        e.printStackTrace();
    } catch (RainedOut e) {
        e.printStackTrace();
    } catch (BaseballException e) {
        e.printStackTrace();
    }       
}

首先构造方法抛出的异常是一致的,atBat在子类重写时修改了抛出异常的类型为Foul的子类,所以子类引用在调用时,只需要捕获PopFoul异常即可;而父类引用在调用时就必须捕获父类方法定义的两个Strike和Foul异常。

好了,关于异常的基本技术细节就介绍到这里,很少写这种纯理论的东西,看着让人昏昏欲睡,但是语言基础就是这么枯燥,想成为一名优秀的程序员,你必须克服!

上一篇下一篇

猜你喜欢

热点阅读