@[toc]
异常概述与异常体系结构
异常概述
异常: 在Java语言中,将程序执行中发生的不正常情况称为 “异常” 。
(开发过程中的语法错误和逻辑错误不是异常)
Java程序在执行过程中所发生的异常事件可分为两类:
Error
(错误) :Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError(栈溢出) 和 OOM(堆溢出) 。一般不编写针对性的代码处理 Error。
// 栈溢出:java.lang.StackOverflowError
main(args); // main方法递归 自己调用自己
// 堆溢出:java.lang.OutOfMemoryError
Integer[] arr = new Integer[1024*1024*1024];
Exception
(异常) :其他因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:空指针访问、试图读取不存在的文件、网络连接中断、数组角标越界 ...
Error 和 Exception 的区别:
Error 通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程; Exception 通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。
对于 Exception 错误,一般有两种解决办法:
- 遇到错误就终止程序的运行。
- 由程序员在编写程序时,就考虑到错误的检测,错误消息的提示,以及错误的处理。
捕获错误最理想的是在编译期间
,但有的错误只有在运行时才会发生。
比如: 除数为0,数组下标越界等
Exception 又分为:
编译时异常
:Exception类中
除
了 RuntimeException类及其子类的其他类
都是编译时异常。- 程序在运行时由于外界因素造成的一般性异常。编译器要求Java程序必须捕获或声明所有编译时异常。
- 对于这类异常,
必须进行处理
,否则程序无法通过编译。
运行时异常
:1. Exception类中的 `RuntimeException 类及其子类`都是运行时异常。 2. 一般是指编程时的逻辑错误,是程序员应该积极避免其出现的异常。 3. 对于这类异常,`可以不作处理`,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。
异常体系结构
所有异常类型都是内置类 Throwable 的子类,因而 Throwable 在异常类的层次结构的顶层。
接下来 Throwable 分成了两个不同的分支。一个分支是Error,它表示不希望被程序捕获或者是程序无法处理的错误;另一个分支是Exception,它表示用户程序可能捕捉的异常情况或者说是程序可以处理的异常。
// ******************以下是编译时异常***************************
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file); // 报错:FileNotFoundException
int data = fis.read(); // 报错:IOException
while(data != -1){
System.out.print((char)data);
data = fis.read(); // 报错:IOException
}
fis.close(); // 报错:IOException
// ******************以下是运行时异常***************************
// ArithmeticException:算数运算异常
int a = 10;
int b = 0;
int c = a / b; // 除0了,运行错
// InputMismatchException:输入不匹配异常,即输入的值数据类型与设置的值数据类型不能匹配
Scanner scanner = new Scanner(System.in);
int score = scanner.nextInt();
System.out.println(score);
scanner.close();
// NumberFormatException:数字格式化异常
String str = "123";
str = "abc";
int num = Integer.parseInt(str); // abc不能转为数字
// ClassCaseException:类型转换异常
Object obj = new Date();
String str = (String)obj; // 两不相关的类之间不能类型转换
// ArrayIndexOutOfBoundsException:数组下标越界异常
public void test2() {
int[] arr = new int[10];
System.out.println(arr[10]);
String str = "abc";
System.out.println(str.charAt(3));
// NullPointerException:空指针异常
int[] arr = null;
System.out.println(arr[3]);
String str = "abc";
str = null;
System.out.println(str.charAt(0));
异常的处理 —— 抓抛模型
过程一:抛
程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并将此对象抛出。
一旦对象抛出后,其后的代码都不再执行。
关于异常对象的产生:
- 系统自动生成的异常对象
- 手动生成一个异常对象,并抛出(throw)。
过程二:抓
可以理解为异常的处理方式:
- try - catch - finally
- throw
异常处理机制一:try-catch-finally
基本结构:
try {
// 可能出现异常的代码
} catch(异常类型1 e) {
// 处理异常的方式
} catch(异常类型2 e) {
// 处理异常的方式
} catch(异常类型3 e) {
//处理异常的方式
}
...
finally {
// 一定会执行的代码
}
说明:
- finally是可选的。根据需求判断到底写不写。
- 使用 try 把可能出现异常的代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去 catch 中进行匹配。
- 一旦 try 中的异常对象匹配到某一个 catch 时,就进入 catch 中进行异常处理。一旦处理完成,就跳出当前的 try - catch 结构(在没写finally的情况下)。继续执行后续代码。
- catch 中的异常类型如果
没有
子父类关系,则谁声明在上,谁声明在下无所谓;
catch 中的异常类型如果满足
子父类关系,则要求子类一定声明在父类的上面。否则就会报错!即,先子类,后父类。
- 常用的异常对象处理方式:
① getMessage()
:获取异常信息,返回值是字符串(必须sout打印才能显示)。
② printStackTrace()
:获取异常类名和 异常信息(所以它包含了getMessage的内容),以及异常出现在程序中的位置。返回值是void(直接调用就显示)。
- 在 try 结构中声明的变量,在出了 try 结构后,就不能在被调用了;如果想在外面调用,就把变量声明在 try 结构外,然后在 try 中赋值。
- try-catch-finally 结构可以嵌套。
体会:
- 使用 try-catch-finally 处理编译时异常,只能保证程序在编译时不再报错,但是运行时仍可能报错。相当于使用 try-catch-finally 将一个编译时可能出现的异常,延迟到运行时出现。
- 由于运行时异常比较常见,所以通常就不对运行时异常编写 try-catch-finally 了。只是对于编译时异常才一定要考虑异常处理。
finally的使用:
- finally 是可选的。根据需求判断到底写不写。
- finally 中声明的是一定会被执行的代码。即使 catch 中又出现异常了。
- 如果 try 或 catch 中有 return,当执行到 return 时,不会立即跳出方法,而是先执行 finally 中的语句,再跳出方法。
- 像数据库连接、输入输出流、网络编程socket等资源,JVM是不能自动回收的,需要自己手动的进行资源的释放。此时的资源释放,就需要声明再 finally 中。
异常处理机制二:throws + 异常类型
如果一个方法可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
throws + 异常类型
写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。
一旦方法体执行时出现了异常,就会在异常代码处生成一个异常类的对象,此对象满足 throws 后异常类型时,就会被抛出。异常代码后续的代码就不再执行!
在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
体会:
try-catch-finally
:真正的将异常处理掉了。throws
:只是将异常抛给了方法的调用者。并没有真正将异常处理掉。
方法重写时,抛出异常类型的规则
子类
重写的方法抛出的异常类型 不大于 父类
被重写的方法抛出的异常类型。
如何选择使用 try-catch-finally 还是使用 throws:
- 如果父类中被重写的方法没有 throws 方式处理异常,则子类重写的方法也不能使用 throws,意味着如果子类重写的方法中有异常,必须使用 try-catch-finally 方式处理。
- 执行的方法a中,先后又调用了另外的几个方法,且这几个方法是递进关系执行的。推荐使用 throws 方式处理这几个方法。而执行的方法a可以考虑使用 try-catch-finally 方式处理。
手动抛出异常
Java异常类对象除在程序执行过程中出现异常时由系统自动生成并抛出,也可根据需要使用人工创建并抛出。
步骤:
- 首先要生成异常类对象
- 然后通过 throw 语句实现抛出操作(提交给Java运行环境)
// 结构
throw new 异常类();
// 举例:
throw new Exception("数据有误!");
自定义异常类
- 一般地,自定义异常类都是 RuntimeException的子类。
- 自定义异常类通常需要编写几个重载的构造器。
- 自定义异常需要提供 serialVersionUID。
- 自定义的异常通过throw抛出。
- 自定义异常最重要的是异常类的名字,当异常出现时,可以根据名字判断异常类型。
如何自定义异常类?
- 继承于现有的异常结构:RuntimeException、Exception 。
- 提供全局常量:serialVersionUID ,每个自定义异常类的全局常量都不一样。
// 这是源码里的Exception,自定义时需要改下面的值
static final long serialVersionUID = -3387516993124229948L;
- 提供重载的构造器。
异常处理总结
异常处理5个关键字: