美文网首页bugstac...
Java异常系列第三集——多态中的异常

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

作者: will4it | 来源:发表于2017-12-25 10:57 被阅读53次

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

    多态在实际项目开发中是经常用到的一个概念,那么多态中的异常是怎样的呢?下面我们就来讨论一下,为了节省时间,我直接用了《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异常。

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

    相关文章

      网友评论

        本文标题:Java异常系列第三集——多态中的异常

        本文链接:https://www.haomeiwen.com/subject/smpyyttx.html