一、异常
1、什么是异常
在java中,程序在运行时出现的不正常情况称为异常,以异常类的形式对这些非正常情况进行封装,通过异常处理机制对程序运行时发生的各种问题进行处理。其实就是java对不正常情况进行描述后的对象体现。
2、java异常类
在Java中提供了大量的异常类,查阅API文档,Throwable 类是 Java 语言中所有错误或异常的超类。只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出。类似地,只有此类或其子类之一才可以是 catch 子句中的参数类型。
Throwable有两个直接的子类Error和Exception,其中,Error代表程序中产生的错误,Exception代表程序中产生的异常。
a) Error类称为错误类,它表示java运行时产生的系统内部错误或资源耗尽的错误,是比较严重的,仅靠修改程序本身是不能恢复执行的。对于Error一般不编写针对性的代码对其进行处理。
b) Exception类称为异常类,它表示程序本身可以处理的错误,在java开发程序中进行的异常处理,都是针对Exception类及其子类。在Exception类的众多子类中有一个特殊的RuntimeException类,该类及其子类用于表示运行时异常,除了此类,Exception类下所有其它的子类都用于表示编译时异常。对于Exception可以采用针对性的代码对其进行处理。
3、Throwable常用方法
String getMessage() :返回此throwable的详细消息字符串
void printStackTrace() :将此throwable及其追踪输出至标准错误流
void printStackTrace(PrintStream s) :将此throwable及其追踪输出到指定的输出流
4、常见异常
RuntimeException子类:
a) java.lang.ArrayIndexOutOfBoundsException
数组下标越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
b)java.lang.ArithmeticException
算术条件异常。比如:整数除零等。
c)java.lang.NullPointerException
空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。比如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等
d)java.lang.ClassNotFoundException
找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
e)java.lang.NegativeArraySizeException 数组长度为负异常
f)java.lang.IllegalArgumentException 非法参数异常
IOException
a)IOException:操作输入流和输出流时可能出现的异常。
b)FileNotFoundException 文件未找到异常
其他
a)ClassCastException 类型转换异常类
b)SQLException 操作数据库异常类
c)NoSuchFieldException 字段未找到异常
d)NoSuchMethodException 方法未找到抛出的异常
e)NumberFormatException 字符串转换为数字抛出的异常
f)StringIndexOutOfBoundsException 字符串索引超出范围抛出的异常
g)IllegalAccessException 不允许访问某类异常
二、运行时异常和编译时异常
异常可以分为编译时被检测异常(该异常在编译时,如果没有处理(没有抛也没有try),编译失败。)和编译时不被检测的异常(运行时异常RuntimeException及其子类。在编译时,不需要处理,编译器不检查。)
1、编译时异常(也称checked异常)
编译时异常的特点是java编译器会对其进行检查,如果出现异常就必须对异常进行处理,否则程序无法通过编译。处理方式有两种:一是使用try..catch语句对异常进行捕获;二是使用throws关键字声明抛出异常,让调用者对其处理。
2、运行时异常(也称unchecked异常)
特点是java编译器不会对其进行检查,当程序中出现这类异常时,即时没有捕获或抛出处理,编译也能通过。运行时异常一般是由于程序中的逻辑错误引起的,在程序运行时无法恢复。
三、异常处理机制
在java应用程序中,对异常的处理要么抛出异常,要么捕获异常。
捕获异常
1、 在Java中,异常通过try-catch语句捕获。其一般语法形式为:
try {
// 可能发生异常的程序代码
} catch (ExceptionType1 e) {
// 捕获并处置try抛出的异常类型Type1
} catch (ExceptionType2 e) {
// 捕获并处置try抛出的异常类型Type2
}
异常捕获处理过程:关键词try后的一对大括号将一块可能发生异常的代码包起来,称为监控区域。Java方法在运行过程中出现异常,则创建异常对象。将异常抛出监控区域之外,由Java运行时系统试图寻找匹配的catch子句以捕获异常。catch语句带一个Throwable类型的参数, 表示可捕获异常类型。当try中出现异常时,catch会捕获到发生的异常,并和自己的异常类型匹配, 若匹配,则执行catch块中代码,并将catch块参数指向所抛的异常对象。catch语句可以有多个, 用来匹配多个中的一个异常,一旦匹配上后则运行其异常处理代码,try-catch语句结束。
匹配的原则是:如果抛出的异常对象属于catch子句的异常类,或者属于该异常类的子类,则认为生成的异常对象与catch块捕获的异常类型相匹配。
2、示例演示:
数学上我们都知道除法中被除数不能为0,我们以此为例。
首先,演示不作任何处理时的情况。
- public class TestException {
- public static void main(String[] args) {
- int x = 5;
- int y = 0;// 除数为0
- System.out.println(x / y);
- }
- }
运行结果为:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at test.TestException.main(TestException.java:8)
系统报了个ArithmeticException: / by zero 算术异常,并且指出是被0除
其次:我们演示捕捉throw语句抛出的除数为0的异常并处理。
- public class TestException {
- public static void main(String[] args) {
- int x = 5;
- int y = 0;// 除数为0
- try {//监控区域
- if (y == 0) {
- throw new ArithmeticException();
- //通过throw语句抛出异常
- }
- System.out.println(x / y);
- } catch (ArithmeticException e) {//捕捉异常
- System.out.println("程序出现异常,除数不能为0");
- }
- }
- }
运行结果为:
程序出现异常,除数不能为0
简单分析:在try监控区域通过if语句进行判断,当“除数为0”的错误条件成立时引发ArithmeticException异常,创建 ArithmeticException异常对象,并由throw语句将异常抛给Java运行时系统,由系统寻找匹配的异常处理器catch并运行相应异常处理代码,打印输出“程序出现异常,除数不能为0”,try-catch语句结束,继续程序流程。实际上,“除数为0”等ArithmeticException,是RuntimException的子类。而运行时异常将由运行时系统自动抛出,不需要使用throw语句。
最后,我们测试捕捉运行时系统自动抛出“除数为0”引发的ArithmeticException异常。
- public class TestException {
- public static void main(String[] args) {
- int x = 5;
- int y = 0;// 除数为0
- try {
- System.out.println(x / y);
- } catch (ArithmeticException e) {//捕捉异常
- System.out.println("程序出现异常,除数不能为0");
- }
- }
- }
运行结果为:
程序出现异常,除数不能为0
简单分析:在运行中出现“除数为0”错误,引发ArithmeticException异常。运行时系统创建异常对象并抛出监控区域,转而匹配合适的异常处理器catch,并执行相应的异常处理代码。由于检查运行时异常的代价远大于捕捉异常所带来的益处,运行时异常不可查。Java编译器允许忽略运行时异常,一个方法可以既不捕捉,也不声明抛出运行时异常。
注意:一旦某个catch捕获到匹配的异常类型,将进入异常处理代码。一经处理结束,就意味着整个try-catch语句结束。其他的catch子句不再有匹配和捕获异常类型的机会。而对于有多个catch子句的异常程序而言,应该尽量将捕获的子类异常的catch子句放在前面,同时尽量将捕获相对高层的父类异常的catch子句放在后面。否则,捕获底层的子类异常的catch子句将可能会被屏蔽。
RuntimeException异常类包括运行时各种常见的异常,RuntimeException异常类的catch子句应该放在最后面,否则可能会屏蔽其后的特定异常处理或引起编译错误。
3、try-catch语句还可以包括第三部分,就是finally子句。它表示无论是否出现异常,都应当执行的内容。
try-catch-finally语句的一般语法形式为:
try {
// 可能发生异常的程序代码
} catch (ExceptionType1 e) {
// 捕获并处置try抛出的异常类型Type1
} catch (ExceptionType2 e) {
// 捕获并处置try抛出的异常类型Type2
}finally{
//无论是否发生异常,都将执行的语句块
}
finally 块:无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。在以下4种特殊情况下,finally块不会被执行:
1)在finally语句块中发生了异常。
2)在前面的代码中用了System.exit()退出程序。
3)程序所在的线程死亡。
4)关闭CPU。
代码示例:
- public class TestException {
- public static void main(String[] args) {
- try {
- int result = divide(4, 0);// 调用divide()方法
- // int result = divide(4, 2);//测试未发生异常情况
- System.out.println(result);
- } catch (Exception e) {
- System.out.println("捕获的异常信息为" + e.getMessage());
- } finally {
- System.out.println("进入finally代码块");
- }
- System.out.println("程序继续向下执行");
- }
- public static int divide(int x, int y)// 定义一个两个整数相除的方法
- {
- int result = x / y;// 定义一个变量result记住相除的结果
- return result;// 将结果返回
- }
- }
运行结果为:
捕获的异常信息为/ by zero
进入finally代码块
程序继续向下执行
未发生异常测试结果为:
2
进入finally代码块
程序继续向下执行
try、catch、finally三个语句块应注意的问题:
a)try、catch、finally三个语句块均不能单独使用,三者可以组成 try...catch...finally、try...catch、 try...finally三种结构,catch语句可以有一个或多个,finally语句最多一个。
b)try、catch、finally三个代码块中变量的作用域为代码块内部,分别独立而不能相互访问。 如果要在三个块中都可以访问,则需要将变量定义到这些块的外面。
c)多个catch块时候,只会匹配其中一个异常类并执行catch块代码,而不会再执行别的catch块, 并且匹配catch语句的顺序是由上到下。
d)必须遵循语句块放置顺序try-catch-finally的顺序。
e)可嵌套 try-catch-finally 结构。比如,try中还可以有try..catch处理
抛出异常
4、throws抛出异常
如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常。例如汽车在运行时可能会出现故障,汽车本身没办法处理这个故障,那就让开车的人来处理。throws语句用在方法定义时声明该方法要抛出的异常类型,如果抛出的是Exception异常类型,则该方法被声明为抛出所有的异常。多个异常可使用逗号分割。throws语句的语法格式为:
methodName([param1,param2,..])throws ExceptionType1[,ExceptionType2..]{}
throws关键字需要写在方法声明的后面,throws后面需要声明方法中发生异常的类型,通常将这种做法称为方法声明抛出一个异常。 使用throws关键字将异常抛给调用者后,如果调用者不想处理该异常,可以继续向上抛出,但最终要有能够处理该异常的调用者。
代码示例:
- public class TestException {
- public static void main(String[] args) {
- try {
- int result = divide(4, 0);
- System.out.println(result);
- } catch (ArithmeticException e) {
- System.out.println("捕获的异常信息为" + e.getMessage());
- } finally {
- System.out.println("进入finally代码块");
- }
- System.out.println("程序继续向下执行");
- }
- public static int divide(int x, int y) throws ArithmeticException// 抛出异常
- {
- int result = x / y;
- return result;
- }
- }
throws抛出异常规则:
a) 如果是运行时异常,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。
b)如果是编译时异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误
c)仅当抛出了异常,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出。
5、使用throw抛出异常
throw总是出现在函数体内,用来抛出一个Throwable类型的异常。程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。 由于异常是异常类的实例对象,我们可以创建异常类的实例对象通过throw语句抛出。该语句的语法格式为:
throw new ExceptionType();
代码示例:
- public class MyException extends Exception {// 创建自定义异常类
- private String message;// 定义String类型变量
- private int value;// 定义int类型变量
- public MyException() {
- super();
- }
- public MyException(String message, int value) {
- this.message = message;
- this.value = value;
- }
- public int getValue() {
- return value;
- }
- public String getMessage() {
- return message;
- }
- }
测试:
- public class TestMyException {
- public static void main(String[] args) {
- try {// 包含可能发生异常的语句
- int result = divide(4, -1);// 调用divide()方法
- System.out.println(result);
- } catch (MyException e) {// 处理自定义异常
- System.out.println(e.getMessage());// 输出异常信息
- System.out.println("错误的负数是:" + e.getValue());// 输出异常值
- } catch (ArithmeticException e) { // 处理ArithmeticException异常
- System.out.println("除数不能为0"); // 输出提示信息
- } catch (Exception e) { // 处理其他异常
- System.out.println("程序发生了其他的异常"); // 输出提示信息
- }
- System.out.println("over");
- }
- public static int divide(int x, int y) throws MyException// 定义方法抛出
- {
- if (y < 0) {// 判断参数是否小于0
- throw new MyException("出现了除数是负数的情况!------/by FuShu", y);// 异常信息
- }
- return x / y;// 返回值
- }
- }
运行结果为:
出现了除数是负数的情况!------/by FuShu
错误的负数是:-1
over
throws和throw的区别:
a)throws使用在函数上。throw使用在函数内。
b)throws后面跟的是异常类,可以跟多个,用逗号隔开。throw后面跟的是异常对象。
四、自定义异常
因为实际项目中会出现特有的问题,而这些问题并未被java所描述并封装对象。所以对于这些特有的问题可以按照java对问题的封装思想。将特有的问题,进行自定义的异常封装。在java中允许用户自定义异常,自定义异常步骤:
(1)、创建自定义异常类,且该类必须继承自Exception或其子类。(因为异常体系有一个特点,因为异常类和异常对象都被抛出。他们都具备可抛性,这个可抛性是Throwable这个体系中的独有特点。)
(2)、在方法中通过throw关键字抛出异常对象。
(3)、如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。
(4)、在出现异常方法的调用者中捕获并处理异常。
自定义异常可参见上面使用throw抛出异常的代码示例。
异常在子父类覆盖中的体现:
(1)、子类在覆盖父类时,如果父类的抛出方法异常,那么子类的覆盖方法,只能抛出父类的异常或该异常的子类
(2)、如果父类方法中抛出多个异常,那么子类在覆盖该方法时,只能抛出父类异常的子集。
(3)、如果父类或接口的方法中没有异常抛出,那么子类在覆盖方法时,也不可以抛出异常。如果子类方法发生了异常,就必须要进行try处理,绝对不能抛。