`
wsmajunfeng
  • 浏览: 491816 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

java的异常限制

阅读更多
    在Java中如果某个方法可能会抛出检查型异常(比如打开一个文件),那么Java编译器会强制在定义该方法的时候必须声明抛出该异常或者该异常的父类异常,否则不能通过编译,这叫做“异常说明”,其形式如void f() throws TooBig,TooSmall{…}。这是种优雅的做法,它使得调用此方法者能确切知道写什么样的代码可以捕获所有潜在的异常。异常限制主要是指针对在继承中当发生覆盖方法的时候,Java编译器要求子类里的覆盖方法中的定义要么没有异常说明,要么有的话则异常说明里的异常必须包含在基类被覆盖方法的异常说明里列出来的那些异常之中。请看下面的代码:
class Exception1 extends Exception{}
class Exception2 extends Exception{}
class Exception3 extends Exception{}
class A{
void f() throws Exception1, Exception2{}
}
public class B extends A{
//1: void f(){System.out.println(“throw no Exception”);}
//2: void f() throws Exception1{System.out.println(“throw Exception1”);}
//3: void f() throws Exception2{System.out.println(“throw Exception2”);}
//4: void f() throws Exception1, Exception2{
//        System.out.println(“throw Exception1”);
//  }
//!5: void f() throws Exception3{System.out.println(“throw Exception3”);}
}

    这段代码中自定义了三个异常Exception1、Exception2和Exception3,类B继承了类A,A中的方法f()声明抛出两个异常Exception1和Exception2。在B中标记了覆盖方法f()的五种书写形式(它们都被注释掉了)。其中标记1、2、3、4所标记的方法f()的书写都是正确的,它们的定义中要么没有异常说明(标记1),要么异常说明里的异常包含在基类方法的异常说明里列出的那些异常之中(标记2、3、4),但是标记5所标记的方法f()是不正确的,不能通过编译器的编译,因为它声明抛出了一个基类方法中没有声明的异常Exception3。
     当然,这只是异常限制很简单的一面,稍后我们将看到更多的异常限制。
1、异常限制的原因
     一个方法的定义中如果有异常说明,那么在调用该方法时需要捕获此方法异常说明中的异常(或父类异常)做相应处理。在Java中的异常捕获处理是通过try-catch语句来实现的,如上文类A中的方法f()处理方式如下:
try{
 f();
}catch(Exception1 exception1){//…
}catch(Exception2 exception2){//…
}

    所以如果我们编写的代码只是同基类A打交道,去调用A中的f(),那么我们就可以将调用f()的语句放入try块中,并随后跟随两个catch语句分别捕捉Exception1和Exception2,而且我们也只需要两个catch语句(因为A中的f()声明只抛出两个异常)。其形式如下:

public class C{
    void g(A a){
try{
           a.f();
}catch(Exception1 exception1){//…
}catch(Exception2 exception2){//…
}
}
}

    类C中的方法g接受一个类A对象的引用作为参数,并调用类A方法f(),我们只需要有两个catch语句去分别捕捉Exception1和Exception2即可。
    现在我们考虑向类C方法g传递的参数不是类A对象的引用而是A的子类对象的引用(这时发生了向上转型),比如是类B对象的引用,那么由于Java的多态性a.f()实际调用的是类B中的方法f()。下面我们来真正的思考为什么Java要对覆盖方法做异常限制:如果说类B中的覆盖方法f()声明抛出了一个基类A中的被覆盖方法f()没有声明抛出的异常Exception3,那么当向类C方法g传递类B对象的引用,然后执行上述代码时,a.f()就可能会抛出异常Exception3,但是随后的异常处理代码(catch语句)中并不能捕获处理异常Exception3,于是程序就失灵了。因此,子类B中的覆盖方法f()绝不能抛出基类A中的被覆盖方法f()没有声明抛出的异常。子类B中的覆盖方法f()要么没有声明抛出异常,要么有的话则声明抛出的异常必须包含在基类被覆盖方法的异常说明里列出的那些异常之中。当然,考虑把上述这种情况换到类实现接口上也是一致的。
    综上所述,Java中的异常限制的基础在于继承和Java中方法的后期绑定(正是由于Java中方法的后期绑定带来了多态性),其根本原因在于为了防止当程序中发生向上转型时可能带来的异常处理错误从而导致的程序的失灵和崩溃。
2、理解异常限制的重要性
    强大的异常处理机制是Java 的一大优势,正确、合理地使用Java 异常处理能够使程序更健壮。异常限制是Java的异常处理机制中一个重要的知识点,而Java的异常限制又是基于继承和多态性的,领悟Java异常限制的内在原因有助于我们更好的理解继承和多态。
    继承是面向对象语言的基本特征之一,用面向对象语言Java编写的稍复杂的程序都会用到继承,而一个可靠健壮的程序也少不了异常处理代码,因此在我们使用Java过程中必然会遇到异常限制的问题,理解异常限制并能熟练运用异常限制对于我们编写正确的Java程序以及提高编程的效率都是十分有利的。
3、异常限制的详细阐述
    分析了异常限制的原因后我们通过一个例子来阐述异常限制的详细内容(代码中被注释掉的都是不能通过编译的):
class A extends Exception{}
class A1 extends A{}
class A2 extends A{}
abstract class B{
     public void B() throws A{}
     public void f() throws A{}
     public void b1() throws A1,A2{}
     public void b2(){}
}
class C extends Exception{}
class A11 extends A1{}
interface D{
public void f() throws C;
}
class BD extends B implements D{
     public BD() throws C,A{}                     //1
     //! public void b2() throws A11{}                //2
     //! public void f() throws C{}                   //3
     public void f(){}                             //4
     public void b1() throws A11{}                  //5
}

    类BD继承了类B,并实现了接口D,因为在子类的构造器中基类的构造器必须首先被调用,所以如果基类的构造器中有异常说明,那么子类构造器也必须有异常说明,并且子类构造器的异常说明里的异常必须包含基类构造器的异常说明里的异常。在上述代码中基类B的构造器声明抛出异常A,因此子类BD的构造器中也必须声明抛出异常A。请看标记1处BD的构造器中的异常说明不仅有A,还有一个基类B的构造器中没有声明的异常C,这样做可以吗?思考一下异常限制的定义,因为异常限制是针对覆盖方法的,而子类中的构造器并没有覆盖基类的构造器,所以这样做显然是可以的,即子类构造器可以声明抛出基类构造器的异常说明里没有的异常。
    类BD中的方法b2()不能通过编译(标记2处),思考一下异常限制的原因,这是因为:类BD中的b2()覆盖了基类B中的b2(),基类B中的b2()没有声明抛出任何异常,而BD的b2()却声明抛出异常A11。如果编译器允许这么做的话,那么当我们在调用B中的b2()的时候不需要做任何异常处理(因为它并没有声明抛出任何异常),而当发生方法的后期绑定把它替换成B的子类BD的对象时,这个方法却有可能会抛出异常A11,但实际上我们并没有做任何异常处理,所以这个时候程序就可能会出现问题了。因此,如果基类的原方法没有异常说明那么子类中的覆盖方法也不能有异常说明。
    类BD中的方法f()很特殊,它的来源有两个,在基类B和接口D中都有方法f()。基类B中的f()声明抛出了异常C,而接口D中的f()没有声明抛出异常,那么子类BD中的f()的异常说明应该是怎样的呢?从代码中我们可以看到声明其抛出异常C是不能通过编译的(标记3处),而没有异常说明是正确的(标记4处)。我们来分析一下原因:因为类BD中f()的来源有两个,它既覆盖了基类B的f()又实现了接口D中的f(),所以它既要遵守基类B的f()带来的异常限制(不能声明抛出异常C以外的异常),又要遵守接口D中的f()带来的异常限制(不能声明抛出异常)。因此综合起来,它能声明抛出的异常就是基类B的f()声明的异常与接口D中的f()声明的异常的“交集”,在这里是个空集,即BD中的f()不能声明抛出异常。于是,标记4处的代码能通过编译,而标记3处的不能。
    最后我们来看类BD中的覆盖方法b1(),基类B中的被覆盖方法b1()声明抛出了异常A1和A2,而BD中的b1()抛出了异常A1的子类异常A11(标记5处),这是允许的,因为如果我们可以捕捉异常A1,那么我们自然也就可以捕捉到它的子类异常A11。因此,子类中的覆盖方法可以声明抛出基类中被覆盖方法声明抛出的异常的子类异常。
4、总结
    根据以上对异常限制的详细阐述,下面我们对异常限制的具体内容做一总结:
   (1)如果基类构造器有异常说明,那么子类构造器也必须声明抛出基类构造器中声明的那些异常(或这些异常的父类异常),另外,子类构造器的异常说明中也可以有基类构造器的异常说明中没有的异常。
   (2)如果基类的被覆盖方法没有异常说明,那么子类里的覆盖方法也不能有异常说明;如果基类的被覆盖方法有异常说明,那么子类里的覆盖方法中的定义要么没有异常说明,要么有的话则异常说明里的异常必须包含在基类被覆盖方法的异常说明里列出的那些异常之中(或是这些异常的子类异常)。
   (3)如果子类不仅继承了一个基类还实现了一个或多个接口,而且该覆盖方法在两个或两个以上的接口(基类)中存在,那么子类中的覆盖方法声明抛出的异常应为存在该方法的那些接口(基类)中的该方法声明抛出的异常的交集。
    总之,如果方法被覆盖,要求被覆盖的方法一定不能声明抛出新的异常或比原方法范畴更广的异常。
1
0
分享到:
评论

相关推荐

    JAVA异常PPT

    主要讲述JAVA中的异常,自己定义异常类,及其使用方法。

    保证Java精确异常的指令调度技术

    Java语言的精确异常要求和Java程序中频繁出现的异常检测严重阻碍或限制了指令调度在Java本地代码编译中的应用,从而减少了代码的指令级并行度。提出的算法可以使指令调度打破Java精确异常要求,能最大程度地发挥作用,...

    JAVA实验十一 异常处理与集合类

    在实验四和实验五中,在银行类中使用数组存放客户,在客户类中使用数组存放帐号,客户和帐号的数量受到了限制,不能随意增加。修改程序,改用ArrayList来存放客户和帐号,最后测试你的程序。 【Account类代码(填写...

    java_异常精讲

    高效的课件,详细的内容介绍,针对性的代码展示,提高性的练习题目。适合新手或者基础薄弱人员学习,一个PPT解决你编程中常见的错误,最重要的是一人下载多人使用,不受限制,没有隐藏密码

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    Java编程老鸟潜心写作,奉献高效率的Java学习心得 完全站在没有编程经验读者的角度,手把手教会读者学习Java 配16小时多媒体教学视频,高效、直观 一一击破Java入门可能会遇到的难点和疑惑 抽丝剥茧,层层推进,让...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    Java编程老鸟潜心写作,奉献高效率的Java学习心得 完全站在没有编程经验读者的角度,手把手教会读者学习Java 配16小时多媒体教学视频,高效、直观 一一击破Java入门可能会遇到的难点和疑惑 抽丝剥茧,层层推进,让...

    Java图书管理系统

    功能:借书、还书、借书上线限制、图书管理(增加、删除、修改)、学生管理(学生借书证注册、信息修改、删除)、管理员等级(超级管理员、图书管理员、学生管理员)、标准的界面设计、防SQL代码注入、异常处理等...

    jce_policy-8 解决aes加解密 key长度限制jar包

    在使用aes加解密时,如果密钥大于128, 会抛出java.security.InvalidKeyException: Illegal key size 异常. 因为密钥长度是受限制的, java运行时环境读到的是受限的policy文件. 文件位于${java_home}/jre/lib/...

    java类文件混合加密算法的研究与分析_邹煜.caj

    是有限制的,在一些较为复杂和长数据加密过程中会存在异常的现象,因此,本 文提出了一种分块Java类文件的RSA加密算法,可有效地对较长数据进行加解 密操作,节约加解密时间,提高算法的效率,具有...

    【09-异常处理】

    •Java的异常被分为两大类:Checked异常和Runtime异常(运行时异常)。所有 RuntimeException类及其子类的实例被称为Runtime异常;不是RuntimeException类及其子类 的异常实例则被称为Checked异常。 Checked...

    Java 语言基础 —— 非常符合中国人习惯的Java基础教程手册

    如果增加某些限制,使 得对数据的访问可按照统一的方式进行,那些能比较容易地产生更为强壮的代码。 OOP 语言提出一种(或称为协议),以保证对数据进行统一的操作。通常的做法是:程 序和对象数据的交互作用通过一...

    JAVA--达内培训笔记

    在文档注释中可以用 @author 表示程序的作者,@version 表示程序的版本,前两个注释符号要写在类定义之前,用于方法的注释@param 对参数进行注释,@return 对返回值进行注释 @throws对抛出异常的注释。 10、...

    java 面试题 总结

    java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。 6、说出Servlet的生命周期,并说出Servlet和CGI的区别。 Servlet被服务器实例化后,容器运行其init方法,...

    excel转pdf,解决aspose100次转换限制问题|excel2PdfNoLicense.zip

    excel转pdf,用新奇的思路解决aspose100次转换限制的问题,仅供学习和参考,请勿商用!水印的问题可以通过添加一张空白图片到pdf上解决!

    Java学习笔记-超强笔记

    实例变量的作用域至少在本类内部,受访问控制符的限制。 在重合作用域,实例变量和局部变量允许有命名冲突,“局部优先”。 定义方法: 格式: [ 修饰符 ] 返回类型 方法名( 参数列表 ) [ throws ...

    Java面试宝典2013版

    Java基础部分基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语法,集合的语法,io 的语法,虚拟机方面的语法。 1、一个".java"源文件中是否可以包括多个类(不是内部...

    Java面试宝典-经典

    45、JAVA语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try块中可以抛出异常吗? 29 46、java中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和suspend()方法...

    Java聊天室源代码

    // 限制用户改变窗口的大小 // 增加关闭窗口的事件处理代码 f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { ds.close();// 程序退出时,关闭Socket,释放相关...

Global site tag (gtag.js) - Google Analytics