掌握:一个图(分类)、五个关键字(try catch finally throws throw)
一、概念
定义:
异常指的是运行期出现的错误(如除0溢出,空指针,数组/字符串下标越界,所要读取的文件不存在),它中断了正在执行的程序的正常指令流。
良好的程序应该在异常发生时提供处理方法,使得程序不会因为异常的发生而阻断或产生不可预见的结果。
补充知识:c和c++中不检查数组越界??如果程序员也没有自己处理数组越界,会存在缓冲区溢出的漏洞,这可能被黑客利用来更改保存现场时的程序地址,运行黑客程序。
二、异常的分类
Throwable是异常的根类。其中,Error类是系统错误(虚拟机错误),我们处理不了的异常;Exception是所有异常类的父类,我们能够处理的,可以catch。Exception中的RuntimeException(也就是uncheckException)是经常出现的错误(如a/b,数组越界等),这种错误系统可以自动检测并处理,可以catch,也可以不catch;其他错误(如IO错误等)必须catch(常见的是JDK中的类方法中写了throws的Exception)。
例1:public static int parseInt(String s,int radix)throws NumberFormatException
所抛出的异常NumberFormatException属于RuntimeException。但是如果程序中不用try-catch处理,虽然可以运行,但是抛出异常后面的语句则无法运行了。
如果用try-catch处理,则抛出异常后,程序依然可以向下运行。
public class Test{ public static void main(String args[]){ try{ String str = "lili"; System.out.println(str+"年龄是:"); int age = Integer.parseInt("20L"); System.out.println(age); }catch(Exception e){ //System.out.println(e.getMessage()); System.out.println(e.toString()); //e.printStackTrace(); } System.out.println("program over"); //不使用try-catch则不输出本句话 }}
输出:
lili年龄是:
java.lang.NumberFormatException: For input string: "20L"program over例2:对于不是RuntimeException的异常。必须写try-catch或用throws向上抛出。否则无法通过编译。如IOException。
三、异常的处理方法
Java程序执行过程中,如果出现异常事件,可以生成一个异常类对象,该异常对象封装了异常事件的信息并将被提交给Java运行时系统,这个过程成为抛出(throw)异常;当Java运行时系统接收到异常对象时,会寻找能够处理这一异常的代码(try-catch),并把当前异常对象交给其处理,这一过程成为捕获(catch)异常;如果没有catch,那么系统自己按照默认方式处理(printStackTrace()方法,打印错误的堆栈信息)。
某个方法抛出异常,既可以在当前方法中try-catch捕获后处理;也可以用throws将异常继续向上抛出,由方法调用者来处理。
1、在当前方法中捕获后处理
异常产生后,如果不做任何处理,程序就会终止,不再执行下面语句。异常捕获结构由try,catch,finally三部分组成。
注意:执行完catch代码块后,继续执行catch代码块后面的的语句,但不会执行try中发生异常语句之后的语句。由此可见,这样就不会因为一个异常影响整个程序的执行。
注意:catch中一定要做出相应处理,哪怕是打印信息也行!多个catch语句时,注意先捕获子类异常再捕获基类异常。
(1)异常处理中3个常用函数:
getMessage() //错误的性质
toString() //异常的类型和性质
printStackTrace() //输出异常的类型、性质、栈层次、出现在程序中的位置
(2)finally语句
完整的异常处理语句一定要包含finally,finally是总会执行的语句。除非以下四种特殊情况发生,finally才不会执行:
(1)在finally语句块中发生了异常
(2)在前面代码中用了System.exit()退出程序
(3)程序所在的线程死亡
(4)关闭CPU
注意:前面的代码中就算有return语句,finally还是会执行!
2、在方法中抛出异常(将异常继续向上抛出)
若某个方法可能发生异常,但不想在当前方法中处理这个异常,那么可以使用throws关键字声明方法抛出异常。
(1)throws应用于声明方法时,用来指定方法可能抛出的异常,多个异常用逗号分隔。
只用throws将异常向上抛出后,如果不想在上一级处理则可以继续抛出,但是最终要有能够处理该异常的代码。
注意:Error,RuntimeException及它们的子类,可以不用throws抛出。但运行时会被系统抛出。
注意:main方法中throws抛出异常非常不好!
(2)异常对象的产生途径有两种:JVM自动生成的和throw手动生成的。也就是说:throw并不是必须的,它一般用于抛出用户自定义异常。
throw用于方法体中,抛出一个异常对象。throw通常用来抛出用户自定义异常。程序执行到throw时立即终止,它后面的语句都不再执行。
通过throw抛出异常后,如果想在上一级捕获处理,则需要在抛出异常的方法中使用throws声明;如果要捕获throw抛出的异常,则必须使用try-catch。(必须catch处理的异常)
四、用户自定义的异常
自定义异常,只需要继承Exception类即可。使用自定义异常类,分一下几步:
(1)通过继承Exception类或RuntimeException类创建自定义异常类
(2)在方法中通过throw抛出异常对象
(3)如果在当前抛出异常的方法中处理异常,可以使用try-catch
(4)否则在方法声明处使用throws向上抛出,在出现异常方法的调用者中使用try-catch
public class Test{ static int quotient(int x,int y) throws MyException{ if(y<0){ throw new MyException("除数不能为负"); } return x/y; } public static void main(String args[]){ try{ int result = quotient(3,-1); }catch(MyException e){ System.out.println(e.getMessage()); }catch(ArithmeticException e){ System.out.println("除数不能为0"); }catch(Exception e){ System.out.println("其他错误"); } }}class MyException extends Exception{ String message; public MyException(String s){ message = s; } public String getMessage(){ //重写getMessage()方法 return message; }}
输出:
除数不能为负
五、重写与异常
class A{ protected void fun(int a,int b) throws IOException{ }}class B extends A{ public void fun(int a,int b) throws FileNotFoundException{ }}
总结:关于java中方法重写(override)的要求:
1、子类中的方法与父类中的方法有相同的返回类型(例如:一个是int,一个是float,则编译出错) 2、子类中的方法与父类中的方法有相同的方法名称 3、子类中的方法与父类中的方法有相同的参数列表 4、子类中的方法的访问级别不能低于父类中该方法的访问级别(举例:父类方法的级别是protected,那么子类重写该方法,其级别必须是protected或者public,一定和父类的访问级别相同或者更宽,否则编译无法通过) 5、子类中方法抛出的异常范围不能大于父类中方法抛出的异常的范围。即重写方法需要抛出与原方法所抛出异常类型一致的异常(相同的异常或子类的异常)或不抛出异常。不能抛出新异常!如果父类不抛异常,子类不能抛异常!
如:原来抛出IOException,重写后抛出FileNotFoundException,但不能抛出Exception(编译出错!)。
个人理解:后两条是为了保证程序的可扩展性。例如一个方法之前没有被重写,现在在其子类中重写该方法:如果子类中的访问权限低于父类,就有可能造成上层调用者无法访问该重写方法的情况;再如如果一个方法重写后抛出的异常是之前父类方法中不包括的类型,那么可能造成调用者无法处理该异常的情况。