深入理解java异常机制
9.深入理解java异常机制
java系统三种处理系统错误:异常,日志,断言
1.Java异常
异常指不期而至的各种状况,如:文件找不到、网络连接失败、非法参数等。异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程。Java通 过API中Throwable类的众多子类描述各种不同的异常。因而,Java异常都是对象,是Throwable子类的实例,描述了出现在一段编码中的 错误条件。当条件生成时,错误将引发异常。
Java异常类层次结构图:
图1 Java异常类层次结构图
在 Java 中,所有的异常都有一个共同的祖先 Throwable(可抛出)。Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性。
Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。
1. Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码e运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。
Exception(异常):是程序本身可以处理的异常。
Exception 类有一个重要的子类 RuntimeException。RuntimeException 类及其子类表示“JVM 常用操作”引发的错误。例如,若试图使用空值对象引用、除数为零或数组越界,则分别引发运行时异常(NullPointerException、ArithmeticException)和 ArrayIndexOutOfBoundException。
注意:异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。
通常,Java的异常(包括Exception和Error)分为可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)。
可查异常(编译器要求必须处置的异常):除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。
不可查异常(编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)和错误(Error)。
Exception 这种异常分两大类运行时异常和非运行时异常(编译异常)。程序中应当尽可能去处理这些异常。
运行时异常:都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。运行时异常会自动抛给上级, 运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
非运行时异常 (编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
2.Java常见异常
2.1. RuntimeException子类:
序号 异常名称 异常描述
java.lang.ArrayIndexOutOfBoundsException 数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
java.lang.ArithmeticException 算术条件异常。譬如:整数除零等。
java.lang.SecurityException 安全性异常
java.lang.IllegalArgumentException 非法参数异常
java.lang.ArrayStoreException 数组中包含不兼容的值抛出的异常
java.lang.NegativeArraySizeException 数组长度为负异常
java.lang.NullPointerException 空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等。
2.IOException
2.2Ioexception
IOException 操作输入流和输出流时可能出现的异常
EOFException 文件已结束异常
FileNotFoundException 文件未找到异常
2.3. 其他 Exception
ClassCastException 类型转换异常类
ArrayStoreException 数组中包含不兼容的值抛出的异常
SQLException 操作数据库异常类
NoSuchFieldException 字段未找到异常
NoSuchMethodException 方法未找到抛出的异常
NumberFormatException 字符串转换为数字抛出的异常
StringIndexOutOfBoundsException 字符串索引超出范围抛出的异常
IllegalAccessException 不允许访问某类异常
InstantiationException 当应用程序试图使用Class类中的newInstance()方法创建
java.lang.ClassNotFoundException 找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
4.处理异常机制
在 Java 应用程序中,异常处理机制为:抛出异常,捕捉异常。 Java代码在某处所抛出的异常。简单地说,异常总是先被抛出,后被捕捉的。
抛出异常:当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。
捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适 的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适 的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。
从方法中抛出的任何异常都必须使用throws子句。捕捉异常通过try-catch语句或者try-catch-finally语句实现。
总体来说,Java规定:对于可查异常必须捕捉、或者声明抛出。允许忽略不可查的RuntimeException和Error。
5.捕获异常:try、catch 和 finally
5.1. try-catch-finally语句
- try {
- // 可能会发生异常的程序代码
- } catch (Type1 id1) {
- // 捕获并处理try抛出的异常类型Type1
- } catch (Type2 id2) {
- // 捕获并处理try抛出的异常类型Type2
- } finally {
- // 无论是否发生异常,都将执行的语句块
- }
5.2. try、catch、finally语句块的执行顺序:
1)当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句;
2)当try捕获到异常,catch语句块里没有处理此异常的情况:当try语句块里的某条语句出现异常时,而没有处理此异常的catch语句块时,此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行;
3)当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句;
5.3. try-catch-finally 规则(异常处理语句的语法规则):
1) 必须在 try 之后添加 catch 或 finally 块。try 块后可同时接 catch 和 finally 块,但至少有一个块。
2) 必须遵循块顺序:若代码同时使用 catch 和 finally 块,则必须将 catch 块放在 try 块之后。
3) catch 块与相应的异常类的类型相关。
4) 一个 try 块可能有多个 catch 块。即Java虚拟机会把实际抛出的异常对象从上到下依次和各个catch代码块声明的异常类型匹配,匹配到了就执行这个catch代码块,不会再执行其他的 catch代码块,应该尽量将捕获底层异常类的catch子 句放在前面,同时尽量将捕获相对高层的异常类的catch子句放在后面
5) 可嵌套 try-catch-finally 结构。
6) 在 try-catch-finally 结构中,可重新抛出异常。
7) 除了下列情况,总将执行 finally 做为结束:JVM 过早终止(调用 System.exit(int));在 finally 块中抛出一个未处理的异常;计算机断电、失火、或遭遇病毒攻击。
5.4例子
- public class TestException {
- public static void main(String args[]) {
- int i = 0;
- String greetings[] = { " Hello world !", " Hello World !! ",
- " HELLO WORLD !!!" };
- while (i < 4) {
- try {
- // 特别注意循环控制变量i的设计,避免造成无限循环
- System.out.println(greetings[i++]);
- } catch (ArrayIndexOutOfBoundsException e) {
- System.out.println("数组下标越界异常");
- } finally {
- System.out.println("--------------------------");
- }
- }
- }
- }
运行结果:
Hello world !
--------------------------
Hello World !!
--------------------------
HELLO WORLD !!!
--------------------------
数组下标越界异常
--------------------------
小结:
try 块:用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。try中遇到异常,try中异常之后的语句不再执行,跳到catch找异常处理器。
catch 块:用于处理try捕获到的异常。catch中没有抛出异常,只是简单处理异常比如e.pring
finally 块:无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。在以下4种特殊情况下,finally块不会被执行:
1)在finally语句块中发生了异常。
2)在前面的代码中用了System.exit()退出程序。
3)程序所在的线程死亡。
4)关闭CPU
例1 捕捉throw语句抛出的“除数为0”异常。
- public class TestException {
- public static void main(String[] args) {
- int a = 6;
- int b = 0;
- try { // try监控区域
- if (b == 0) throw new ArithmeticException(); // 通过throw语句抛出异常
- System.out.println("a/b的值是:" + a / b);
- }
- catch (ArithmeticException e) { // catch捕捉异常
- System.out.println("程序出现异常,变量b不能为0。");
- }
- finally{
- System.out.println("执行finally语句"); }
- System.out.println("程序正常结束。");
- }
- }
运行结果:程序出现异常,变量b不能为0。
程序正常结束。
例1 在try监控区域通过if语句进行判断,当“除数为0”的错误条件成立时引发ArithmeticException异常,创建 ArithmeticException异常对象,并由throw语句将异常抛给Java运行时系统,由系统寻找匹配的异常处理器catch并运行相应异 常处理代码,打印输出“程序出现异常,变量b不能为0。”try-catch语句结束,继续程序流程。
事实上,“除数为0”等ArithmeticException,是RuntimException的子类。而运行时异常将由运行时系统自动抛出,不需要使用throw语句。
6 抛出异常
任何Java代码都可以抛出异常,如:自己编写的代码、来自Java开发环境包中代码,或者Java运行时系统。无论是谁,都可以通过Java的throw语句抛出异常。从方法中抛出的任何异常都必须使用throws子句,该方法不处理异常,交给调用该方法的方法去处理。使用throws关键字将异常抛给调用者后,如果调用者不想处理该异常,可以继续向上抛出,但最终要有能够处理该异常的调用者。
1. throws抛出异常
如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常。例如汽车在运行时可能会出现故障,汽车本身没办法处理这个故障,那就让开车的人来处理。
throws语句用在方法定义时声明该方法要抛出的异常类型,如果抛出的是Exception异常类型,则该方法被声明为抛出所有的异常。多个异常可使用逗号分割。throws语句的语法格式为:
- methodname throws Exception1,Exception2,..,ExceptionN
- {
- throw new Exception1("异常信息"); //抛出异常
- }
Throws抛出异常的规则:
1) 如果是不可查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。
2)必须声明方法可抛出的任何可查异常(checked exception)。即如果一个方法可能出现受可查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误
3)仅当抛出了异常,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出,而不是囫囵吞枣。
4)调用方法必须遵循任何可查异常的处理和声明规则。若覆盖一个方法,则不能声明与覆盖方法不同的异常。声明的任何异常必须是被覆盖方法所声明异常的同类或子类。
例如:
- void method1() throws IOException{} //合法
- //编译错误,必须捕获或声明抛出IOException
- void method2(){
- method1();
- }
- //合法,声明抛出IOException
- void method3()throws IOException {
- method1();
- }
- //合法,声明抛出Exception,IOException是Exception的子类
- void method4()throws Exception {
- method1();
- }
- //合法,捕获IOException
- void method5(){
- try{
- method1();
- }catch(IOException e){…}
- }
- //编译错误,必须捕获或声明抛出Exception
- void method6(){
- try{
- method1();
- }catch(IOException e){throw new Exception();}
- }
- //合法,声明抛出Exception
- void method7()throws Exception{
- try{
- method1();
- }catch(IOException e){throw new Exception();}
- }
2. 使用throw抛出异常
throw总是出现在函数体中,用来抛出一个Throwable类型的异常。程序会在throw语句后立即终止,当然会执行finally的语句,finally括号后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try, 如果所有方法都层层上抛获取的异常,最终JVM会进行处理,处理也很简单,就是打印异常消息和堆栈信息。如果抛出的是Error或RuntimeException,则该方法的调用者可选择处理该异常。
public static int med1() throws ArithmeticException{
try {
int a = 3/0;
} catch (ArithmeticException e) {
System.out.println("异常");
throw new ArithmeticException("跑出去");
} finally {
System.out.println("finally信息"); }
System.out.println("会不会执行"); //throw异常之后就不会不会执行了
return 33; }
public static void main(String[] args) {
try {
med1();
}catch (ArithmeticException e){
System.out.println("扑捉到异常");
} }}
结果:
异常
finally信息
扑捉到异常
4.3 异常链
Java这种向上传递异常信息的处理机制,形成异常链。Java方法抛出的可查异常将依据调用栈、沿着方法调用的层次结构一直传递到具备处理能力的调用方法,最高层次到main方法为止。如果异常传递到main方法,而main不具备处理能力,也没有通过throws声明抛出该异常,将可能出现编译错误。
4.4 Throwable类中的常用方法
注意:catch关键字后面括号中的Exception类型的参数e。Exception就是try代码块传递给catch代码块的变量类型,e就是变量名。catch代码块中语句"e.getMessage();"用于输出错误性质。通常异常处理常用3个函数来获取异常的有关信息:
getCause():返回抛出异常的原因。如果 cause 不存在或未知,则返回 null。
getMeage():返回异常的消息信息。
printStackTrace():对象的堆栈跟踪输出至错误输出流,作为字段 System.err 的值。
有时为了简单会忽略掉catch语句后的代码,这样try-catch语句就成了一种摆设,一旦程序在运行过程中出现了异常,就会忽略处理异常,而错误发生的原因很难查找。
5.自定义异常
class MyException extends Exception { // 创建自定义异常类
String message; // 定义String类型变量
public MyException(String ErrorMessagr) { // 父类方法
message = ErrorMessagr;
}
public String getMessage() { // 覆盖getMessage()方法
return message;
}
}
使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类即可。
在程序中使用自定义异常类,大体可分为以下几个步骤。
(1)创建自定义异常类。
(2)在方法中通过throw关键字抛出异常对象。
(3)如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。
(4)在出现异常方法的调用者中捕获并处理异常。
常见RuntimeException:NullPointException、ArrayIndexOutOfException、ArithmeticException、ArrayStorException、IllgalArgumentException、NegativeArraySizeException、ClassCastException,自动抛出
-
异常构造
Exception()
Exception(String mess,Throwable cause) 发生异常的输出信息和引起的原因
Exception(String message)
Exception(Throwable cause)
注意:子类抛出父类相同的异常或者异常的子类,不能抛出新的异常。
4.带资源的try:finally中一般是在遇到异常后处理try中资源的关闭,但是有时finally中close方法也会抛出异常,这样就显得太繁琐,try中写需要关闭的资源,不论遇到异常没,都会自动close资源,而且close方法抛出的异常会被压抑。由addSupppressed方法增加到原来的异常中,可以调getSuppressed()获取被压抑的异常。类实现了autoCloseable接口
try(File out=new File(),Scanner s=new Scanner(System.in)){
代码;
}cathch(Exception){}
38、try {}或者catch{}里有一个return语句,那么紧跟在这个try后的finally {}里的code会不会被执行,什么时候被执行,在return前还是后?return 和throw功能差不多。都代表结束。
也许你的答案是在return之前,但往更细地说,我的答案是在return中间执行,请看下面程序代码的运行结果:
public class Test {
/**
* @param args add by zxx ,Dec 9, 2008
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(new Test().test());;
}
static int test()
{
int x = 1;
try
{
return x;
}
finally
{
++x;
} }}
---------执行结果 ---------
1
运行结果是1,为什么呢?主函数调用子函数并得到结果的过程,好比主函数准备一个空罐子,当子函数要返回结果时,先把结果放在罐子里,然后再将程序逻辑返回到主函数。所谓返回,就是子函数说,我不运行了,你主函数继续运行吧,这没什么结果可言,结果是在说这话之前放进罐子里的。
catch{}中有return,这时候虽然有异常,但是正常返回3了,那么主函数中就扑捉不到异常,return表示正常返回。
public static int med1(){
try {
int a = 3/0;
} catch (ArithmeticException e) {
return 3;
} finally {
System.out.println("finally信息"); }
System.out.println("会不会执行");
return 33; }
public static void main(String[] args) {
try {
med1();
}catch (ArithmeticException e){
System.out.println("扑捉到异常");
} }}
结果:
finally信息
断言:用在测试开发阶段,发布后不会加载断言。
assert 条件 : 表达式; assert 条件;两种形式,当条件失败后,会抛出一个assertionError异常,使用表达式的作为其构造器的字符串信息。

浙公网安备 33010602011771号