异常就跟生活中的人一样,如果身体的某部分出现了问题,就会反馈给身体一种信号,例如:疼痛、头晕等等
在程序中也是一样的,在日常开发中,绞尽脑汁写的代码不可能完美,在程序运行过程中难免会出现一些奇奇怪怪的问题
通过代码很难找出,所以需要异常来提示那部分出现问题,方便查找
比如常见的异常:
算术异常
数组越界异常
空指针异常
输入不匹配
java中不同类型的异常,都有与其对应的类来进行描述
Java中异常的种类繁多,所以对不同异常或者错误进行了分类管理
下面只是写了一部分的种类,如果想知道运行时异常的所种类
可以到Java的帮助手册
里的 RuntimeException
里查找
从上面图片的信息可以得知:
Throwable
:是异常体系的顶层类
,其派生出两个重要的子类, Error
和 Exception
Error
:指的是Java虚拟机无法解决的严重问题,比如:JVM的内部错误、资源耗尽等StackOverflowError
和OutOfMemoryError
,一旦出现程序就会崩溃Exception
:异常产生后程序员可以通过代码进行处理,使程序继续执行。比如:感冒、发烧。Exception
程序中出现异常可以根据执行的时间,可以将异常分为二种:
在程序编译期间发生的异常,称为编译时异常,也称为受检查异常(Checked Exception)
在程序执行期间发生的异常,称为运行时异常,也称为非受检查异常(Unchecked Exception)
RunTimeException以及其子类对应的异常,都称为运行时异常
比如:NullPointerException
、ArrayIndexOutOfBoundsException
、ArithmeticException
注意:
编译时出现的语法性错误,不能称之为异常
例如:
把变量名写错或者拼错,编译过程中就会出错, 这是 编译期
出错
而运行时指的是程序已经编译通过得到 class
文件了, 再由 JVM
执行过程中出现的错误
在Java中,异常处理主要的5个关键字:throw
、try
、catch
、final
、throws
正常如果出现异常是不会在继续执行后面的代码的,程序是立即终止
例如:
但是如果进过异常处理,是可以执行后面的代码的
处理异常的方法有二种:
LBYL
: Look Before You Leap,在操作之前就做充分的检查
每执行一步,判断一次
缺陷:正常流程和错误处理流程代码混在一起, 代码整体显的比较混乱
这种很少使用
EAFP
: It’s Easier to Ask Forgiveness than Permission
先操作, 遇到问题再处理,把要执行的代码都放到一起,后面遇到问题在处理
优势:正常流程和错误流程是分离开的, 程序员更关注正常流程,代码更清晰,容易理解代码
例如:
public static void main(String[] args) {
try {
System.out.println(10/0);
System.out.println("asdasda"); //上面出现异常是不会执行这条代码
}catch (ArithmeticException e){
System.out.println("算术异常!");
e.printStackTrace();//快速的定位出现异常的位置
}
System.out.println("执行后续代码!");
}
结果:
异常的捕获也就是异常的具体处理方式
,主要有两种:异常声明throws
以及 try-catch
捕获处理
throws
并没有对异常进行处理,而是将异常报告给抛出,由调用者处理
如果要对异常进行处理就要使用 try-catch
语法形式:
try{
//可能出现异常的所有代码
}catch(异常类型 变量名){
//如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致的异常
//或者是try中抛出异常的基类时,就会被捕获到
}catch(异常类型 变量名){
// 情况同上
}
具体使用方式:
public static void main(String[] args) {
try {
int[] arr = {1, 2, 3};
System.out.println(arr[5]);
System.out.println("abcdef");// 上面出现异常后,这条代码不会被执行
}catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
System.out.println("数组越界异常!");
}
System.out.println("执行后续代码!");
}
结果:
catch
时异常类型不匹配
,即异常不会被成功捕获
,也就不会被处理public static void main(String[] args) {
try {
int[] arr = {1, 2, 3};
System.out.println(arr[5]); // 这里是数组越界异常!
}catch (ArithmeticException e){ //这里捕获的是算术异常
e.printStackTrace();
System.out.println("算术异常!");
}
System.out.println("执行后续代码!");
}
结果:
public static void main(String[] args) {
try {
int[] arr = {1, 2, 3};
System.out.println(arr[5]);
System.out.println("abcdef");// 上面出现异常后,这条代码不会被执行
}catch (ArithmeticException e){
e.printStackTrace();
System.out.println("算术异常!");
}catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
System.out.println("数组越界异常!");
}catch (NullPointerException e){
e.printStackTrace();
System.out.println("空指针异常!");
}
System.out.println("执行后续代码!");
}
如果多个异常的处理方式是完全相同, 也可以写成这样:
用 |
隔开
try{
...
}catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
...
}
如果异常之间具有父子关系,一定是子类异常在前catch,父类异常在后catch,否则语法错误:
例如:
但是如果把位置换一下,就可以通过
可以通过一个catch捕获所有的异常,即多个异常,一次捕获(不推荐)
例如:
public static void main(String[] args) {
try {
int[] arr = {1, 2, 3};
System.out.println(arr[5]); // 这里是数组越界异常!
}catch (Exception e){
e.printStackTrace();
System.out.println("捕获所有异常!");
}
System.out.println("执行后续代码!");
}
由于 Exception
类是所有异常类的父类. 因此可以用这个类型表示捕捉所有异常
关于异常的处理方式
异常的种类有很多, 我们要根据不同的业务场景来决定.
对于比较严重的问题(例如和算钱相关的场景), 应该让程序直接崩溃, 防止造成更严重的后果
对于不太严重的问题(大多数场景), 可以记录错误日志, 并通过监控报警程序及时通知程序猿
对于可能会恢复的问题(和网络相关的场景), 可以尝试进行重试.
在我们当前的代码中采取的是经过简化的第二种方式.我们记录的错误日志是出现异常的方法调用信息,
能很快速的让我们找到出现异常的位置. 以后在实际工作中我们会采取更完备的方式来记录异常信息.
throw
是手动抛出一个异常throw
+ 具体的异常对象(只能抛出一个)throws
是声明一个异常throws
+ 异常的类型(可以是多个异常类型,用,
来拼接)以上讲的都是程序编译时或者运行时自动进行
抛出异常的
那么是否可以主动通过代码抛出异常呢
主动抛出异常要使用到throw 声明异常
例如:
以下必须是在运行时异常使用
方法一:在主函数处理异常
public static void test(int a){
throw new NullPointerException();
}
public static void main(String[] args) {
try {
test(10);
}catch (NullPointerException e){
e.printStackTrace();
}
System.out.println("执行后续代码!");
}
方法二:在方法里处理异常
public static void test(int a){
try {
if(a == 10)
throw new NullPointerException();
}catch(NullPointerException e){
e.printStackTrace();
}
System.out.println("执行后续代码!");
}
public static void main(String[] args) {
test(10);
System.out.println("执行后续代码!");
}
如果是编译时异常(受查异常)
可以使用 throws
进行声明异常,但是下面并没有处理异常
正确方法:
public static void test2(int a) throws CloneNotSupportedException {
if (a == 10) {
throw new CloneNotSupportedException();//
}
System.out.println("执行后续代码!");
}
public static void main(String[] args) {
try {
test2(10);
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
System.out.println("执行后续代码!");
}
注意:
1. throw
必须写在方法体内部
2. 抛出的对象必须是Exception
或者 Exception 的子类对象
3. 如果抛出的是 RunTimeException
或者 RunTimeException
的子类,则可以不用处理,直接交给JVM
来处理
4. 如果抛出的是编译时异常,用户必须处理,否则无法通过编译
5. 异常一旦抛出,其后的代码就不会执行
在写程序时,有些特定的代码,不论程序是否发生异常,都需要执行
比如:程序中打开的一些资源:网络连接、数据库 连接、IO流等
如果在程序正常或者异常退出时,必须要对资源进行回收
如果没有进行回收,就会造成资源泄漏,所以要使用finally
来解决
finally
中的代码一定会执行的,一般在finally中进行一些资源的扫尾工作
语法格式:
try{
// 可能会发生异常的代码
}catch(异常类型 变量名){
// 对捕获到的异常进行处理
}finally{
// 此处的语句无论是否发生异常,都会被执行到
}
如果没有抛出异常,或者异常被捕获处理了,finally的代码也会执行
例如:
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
try{
int n = scanner.nextInt();
}catch(InputMismatchException e){
e.printStackTrace();
System.out.println("数据不匹配!");
}finally{
scanner.close(); //关闭资源
System.out.println("不管有没有异常,finally都会被执行!");
}
}
既然知道finally
是用来关闭资源的,那么不使用和使用finally
的区别在哪里呢?
正确的方法是把要关闭的资源,放到 finally
里来执行
例如:
public static int getData(){
Scanner sc = null;
try{
sc = new Scanner(System.in);
int data = sc.nextInt();
return data;
}catch (InputMismatchException e){
e.printStackTrace();
}finally {
System.out.println("finally中代码");
//一定会被执行
sc.close();
}
System.out.println("try-catch-finally之后代码");
return 0;
}
也可以通过下面这种方式让程序自动关闭资源:
这是把资源交给 try
来关闭,当使用完后编译器会自动的回收
public static int getData(){
try(Scanner sc = new Scanner(System.in)){
int data = sc.nextInt();
return data;
}catch (InputMismatchException e){
e.printStackTrace();
}finally {
System.out.println("finally中代码");
}
System.out.println("try-catch-finally之后代码");
return 0;
}
finally 执行的时机是在方法返回之前,但是如果 finally 中也存在 return 语句, 那么就会执行 finally 中的 return,从而不会执行到 try 中原有的 return,但是在 finally 中写 return 会被编译器当做一个警告
try
中的代码,如果 try
中的代码出现异常,就会结束try
中后续的代码catch
中寻找是否有匹配的异常类型,如果有就会执行 catch
中的代码finally
中的代码都会被执行到(在该方法结束之前执行)
main
函数都没有处理异常,就会交给 JVM
处理,此时程序就会异常终止Java 中虽然已经内置了丰富的异常类, 但是并不能完全表示实际开发中所遇到的一些异常
此时就需要根据实际情况的来自定义异常结构
自定义方式:
Exception(编译时)
或者 RunTimeException(运行时)
String
类型参数的构造方法,参数含义:出现异常的原因注意:
自定义异常通常会继承自 Exception
或者 RuntimeException
继承自 Exception
的异常默认是受查异常
继承自 RuntimeException
的异常默认是非受查异常
例如:
定义异常类
手动抛出异常信息、