学习16-异常
1. 异常概述
1.1 什么是异常?有什么用?
当程序执行过程中出现了不正常的情况,这种不正常的情况就叫做***异常***
如果一门语言在程序出现了异常时,没有提示任何信息,那么这门语言就是失败的语言。
Java语言提供了异常处理机制,在程序出现异常时,jvm将该异常信息打印输出到控制台,供程序员参考。程序员根据异常信息修改程序,增加程序的健壮性。
/**
* @Author TSCCG
* @Date 2021/6/3 15:32
*/
public class ExceptionDemo01 {
public static void main(String[] args) {
int i = 20;
int j = 0;
int num = i / j;
System.out.println(num);
}
}
运行:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at ExceptionDemo01.main(ExceptionDemo01.java:10)
1.2 异常的存在形式
在Java中异常是以类和对象的形式存在的,任何一个异常类都可以创建异常对象
/**
* @Author TSCCG
* @Date 2021/6/3 15:32
*/
public class ExceptionDemo01 {
public static void main(String[] args) {
//创建异常对象
ArithmeticException ex = new ArithmeticException("发生了异常!");
System.out.println(ex);
}
}
结果:
java.lang.ArithmeticException: 发生了异常!
当发生异常时,jvm就会自动创建一个异常对象,然后抛出
public class ExceptionDemo01 {
public static void main(String[] args) {
//JVM执行到此处时,检查到异常,创建异常对象: new ArithmeticException("/ by zero");
//然后JVM将创建的对象抛出,打印输出信息到控制台。
System.out.println(50/0);
}
}
2. 异常的分类
- Object(顶级父类)
- Throwable(可抛出的)
- Exception(可处理的)
- Exception除RuntimeException及其所有子类外的其他所有子类都叫编译时异常。(编译时异常不是在编译阶段发生的异常,而是表示必须在编译程序的时候预先对这种异常进行处理,如果不处理,编译器就报错)
- RuntimeException及其所有子类都叫运行时异常。(运行时异常在编写程序阶段可以选择处理,也可以选择不处理)
- Error(不可处理,直接退出JVM)
- Exception(可处理的)
- Throwable(可抛出的)
所有的异常都发生在运行阶段,因为只有当程序运行时才能创建异常对象
异常继承UML图:
UML图链接:https://www.processon.com/view/60bb6adc07912941e204c79d
脑图:
脑图链接:https://www.processon.com/view/60bc1d811e08533a509c0b5f#map
2.1 编译时异常 (checkedException)
Exception除了RuntimeException及其所有子类外,其他所有子类都叫做编译时异常。编译时异常也叫受检异常(CheckedException)或受控异常。
编译时异常不是在编译阶段发生的异常,而是表示必须在编译程序的时候预先对这种异常进行处理,如果不处理,编译器就报错,无法运行。
编译时异常发生的概率较高,就好比出门时外面正在下暴雨。如果不打伞,那么生病的概率就很高,为了避免生病,就要提前准备一把雨伞。
常见的编译时异常:
异常类名 | 释义 |
---|---|
IOException | 输入输出流异常 |
FileNotFoundException | 文件找不到的异常 |
ClassNotFoundException | 类找不到异常 |
DataFormatException | 数据格式化异常 |
NoSuchFileldException | 没有匹配的属性异常 |
NoSuchMethodException | 没有匹配的方法异常 |
SQLException | 数据库操作异常 |
TimeoutException | 执行超时异常 |
2.2 运行时异常 (uncheckedException)
RuntimeException及其所有子类都是运行时异常。运行时异常也叫未受检异常(UnCheckedException)或非受控异常。
运行时异常在编写程序阶段可以选择处理,也可以选择不处理。
运行时异常发生的概率较低,就好比出门时阳光明媚,之后突然下起了暴雨,无法提前预知。你在出门前不知道天气如何,可以提前准备一把雨伞,也可以不准备。
常见的运行时异常:
异常类名 | 释义 |
---|---|
IndexOutOfBoundsException | 抛出以表示某种索引(例如数组,字符串或向量)的索引超出范围 |
ClassCastException | 类型转换异常 |
NullPointerException | 空指针异常 |
IllegalAccessException | 非法的参数异常 |
InputMismatchException | 输入不匹配异常 |
ArithmeticException | 抛出异常算术条件时抛出。 例如,“除以零”的整数会抛出此类的一个实例 |
3. 异常的处理
Java语言中对异常的处理有两种方式:
- 使用try…catch语句将异常捕捉。
- 在方法声明的位置上,使用throws关键字将异常抛给上一级。谁调用我,我就抛给谁。
举例说明:
我是某饭店的一名服务员,一个失误,导致将菜拍在了顾客的脸上。顾客不满,要求索赔。”顾客要求索赔“可以看作是一个异常发生了。此时,我有两种处理方式:
-
我自己掏腰包对顾客进行赔偿。(异常的捕捉)
-
将这件事告诉我的上级领导(经理)。(异常上抛)
我 ----(上抛)----> 经理 ----(上抛)----> 老板
异常发生后,如果我选择将异常上抛给我的调用者(经理),那么我的调用者需要对这个异常继续进行处理,同样有这两种处理方式。
注意:
Java中异常发生后如果一直进行上抛,最终抛给main方法,main方法继续上抛,抛给调用者JVM,JVM知道这个异常发生,只会发生一种结果,那就是终止Java程序的执行。
3.1 try…catch
当程序发生异常时,会立即停止运行,无法继续向下执行。为了保证程序能有效的执行,Java中提供了一种对异常进行处理的方式——异常捕获。
3.1.1 异常捕获try…catch语句基本语法格式
try {
//可能发生异常的语句
} catch (需要进行捕获的异常类型创建的异常对象) {
//对捕获的异常进行相应处理
}
例子:
public class ExceptionDemo01 {
public static void main(String[] args) {
System.out.println(50/0);
System.out.println("后面的语句");
}
}
运行结果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at ExceptionDemo01.main(ExceptionDemo01.java:3)
System.out.println(“后面的语句”);这行代码没有运行
可见当发生异常后如果不去解决就会影响后续语句的执行
使用try…catch对以上代码进行处理:
public class ExceptionDemo01 {
public static void main(String[] args) {
/*
程序先运行try中的语句,如果没有发生异常,程序正常执行,不会执行catch中语句,
如果发生了异常,会与catch中创建的异常对象进行匹配
如果匹配,执行catch及后续语句
如果不匹配,程序终止,不会执行catch及后续语句
*/
try {
System.out.println(50/0);
} catch (ArithmeticException a) {
//打印异常信息
a.printStackTrace();
}
System.out.println("后面的语句");
}
}
java.lang.ArithmeticException: / by zero
at ExceptionDemo01.main(ExceptionDemo01.java:10)
后面的语句
3.1.2 catch中创建异常对象的类型
catch后面小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类型
import java.io.FileInputStream;
import java.io.FileNotFoundException;
/**
* @Author: TSCCG
* @Date: 2021/06/13 17:45
*/
public class ExceptionDemo04 {
public static void main(String[] args) {
System.out.println("main start");
try {
//创建输入流
FileInputStream file = new FileInputStream("D:\\study\\实验系统网址密码.TX");
} catch (IOException e) {//FileNotFoundException的父类,也可以写所有异常的父类Exception
System.out.println("找不到文件");
}
System.out.println("main end");
}
}
3.1.3 catch可以写多个
catch可以写多个,建议捕获异常的时候,一个一个精确处理,这样有利于程序的调试
第一种方法:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* @Author: TSCCG
* @Date: 2021/06/13 17:45
*/
public class ExceptionDemo04 {
public static void main(String[] args) {
System.out.println("main start");
try {
//创建输入流
FileInputStream file = new FileInputStream("D:\\study\\实验系统网址密码.TX");
//读文件
file.read();
} catch (FileNotFoundException e) {
System.out.println("找不到文件");
} catch (IOException e) {
System.out.println("读不到文件");
}
System.out.println("main end");
}
}
第二种方法:
jdk8的新特性,可以在catch中以 "异常类型 | 异常类型| 异常类型 变量 "的形式来创建异常对象
import java.io.FileInputStream;
import java.io.FileNotFoundException;
/**
* @Author: TSCCG
* @Date: 2021/06/13 17:45
*/
public class ExceptionDemo04 {
public static void main(String[] args) {
System.out.println("main start");
try {
//创建输入流
FileInputStream file = new FileInputStream("D:\\study\\实验系统网址密码.TX");
System.out.println(50 / 0);
} catch (FileNotFoundException | ArithmeticException | NullPointerException e) {
System.out.println("找不到文件 or 数字异常 or 空指针异常");
}
System.out.println("main end");
}
}
3.1.4 catch写多个的时候,必须遵守从小到大
catch写多个的时候,创建异常对象的级别,从上到下,必须遵守从小到大的原则
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* @Author: TSCCG
* @Date: 2021/06/13 17:45
*/
public class ExceptionDemo04 {
public static void main(String[] args) {
System.out.println("main start");
try {
//创建输入流
FileInputStream file = new FileInputStream("D:\\study\\实验系统网址密码.TX");
//读文件
file.read();
} catch (IOException e) {
System.out.println("读不到文件");
} catch (FileNotFoundException e) { //报错
System.out.println("找不到文件");
}
System.out.println("main end");
}
}
IOException是FileNotFoundException的父类,当创建FileInputStream对象出现异常时,第一个catch中的IOException可以对其进行捕获。执行第二个catch时,由于异常已经被捕获,无法再次进行捕获,故编译器报错。
3.1.5 finally
3.1.5.1 语法格式
finally无法单独使用,必须跟在try…catch语句后面。
try {
//可能发生异常的语句
} catch (IOException e) {
e.printStackTrace();
} finally {
}
3.1.5.2 finally适用场景
通常在finally语句块中完成资源的释放/关闭,因为finally中的代码比较有保障。
例子:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* @Author: TSCCG
* @Date: 2021/06/13 21:18
*/
public class ExceptionDemo06 {
public static void main(String[] args) {
FileInputStream file = null;
try {
//创建输入流对象
file = new FileInputStream("D:\\study\\实验系统网址密码.TXT");
//开始读文件。。。
String s = null;
s.toString();//这里一定会报空指针异常
//流使用完需要关闭,因为流是占用资源的。如果上面的代码出现异常,那么流就无法关闭,这是非常危险的
file.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
结果:
Exception in thread "main" java.lang.NullPointerException
at ExceptionDemo06.main(ExceptionDemo06.java:17)
使用finally进行处理
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* @Author: TSCCG
* @Date: 2021/06/13 21:18
*/
public class ExceptionDemo06 {
public static void main(String[] args) {
FileInputStream file = null;
try {
//创建输入流对象
file = new FileInputStream("D:\\study\\实验系统网址密码.TXT");
//开始读文件。。。
String s = null;
s.toString();//这里一定会报空指针异常
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
//不管是否发生异常,finally里的语句都会执行。
System.out.println("阿巴巴巴");
try {
//如果创建流对象失败,那么file在这里仍会是null,会报空指针异常。所以使用if判断避免
if (file != null ) {
//close()方法有异常,采用捕捉的方式
file.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
结果:
阿巴巴巴
Exception in thread "main" java.lang.NullPointerException
at ExceptionDemo06.main(ExceptionDemo06.java:18)
3.1.5.3 finally语句段在程序中的执行
finally语句一定会执行
try和finally,没有catch也可以执行
例子:
public static void main(String[] args) {
try {
System.out.println("---try---");
//结束方法
return;
} finally {
//finally语句即使在try中存在return语句也会执行
System.out.println("---finally---");
}
//以下执行不到,因为在try中执行了return,方法结束
System.out.println("Hello");
}
结果:
---try---
---finally---
以上代码执行顺序:
先执行—try—
再执行—finally—
最后执行return(return只要执行,方法一定会结束)
3.1.5.4 finally面试题
以下代码输出什么数?
/**
* @Author: TSCCG
* @Date: 2021/06/14 09:36
*/
public class ExceptionDemo09 {
public static void main(String[] args) {
System.out.println(m1());
}
public static int m1() {
int i = 100;
try {
return i;
}finally {
i++;
}
}
}
结果:
100
java中语法规则:
- 方法体中的代码必须遵守自上而下的顺序依次逐行执行
- return语句一旦执行,整个方法必须结束
return i; 语句出现在int i = 100后面,所以最终结果必须是返回100
return语句必须保证是最后执行的,一旦执行,整个方法结束。
反编译class文件:
public static int m1() {
int i = 100;
try {
int n = i;
return n;
}
finally {
++i;
}
}
可见,在try语句中先将i赋给一个临时变量n,然后执行finally语句中的++i,最后将n返回
3.1.5.5 退出JVM finally语句不执行
public static void main(String[] args) {
try {
System.out.println("---try---");
System.exit(0);
} finally {
System.out.println("---finally---");
}
}
结果:
---try---
3.2 throws关键字
一般在程序开发中,开发者通常会意识到程序可能出现的问题,可以直接通过try…catch对异常进行捕获处理。但有些时候,方法中的代码是否会出现异常,开发者并不明确或不急于处理,为此,Java允许将这种异常从当前方法中抛出,然后让后续的调用者在使用时进行异常处理。
格式:
一般将throws关键字写在方法声明的后面,并需要在后面声明方法中发生的异常类型
例子:
main方法将异常继续上抛给JVM
/**
* @Author: TSCCG
* @Date: 2021/06/13 11:14
* throws关键字
* 用来在方法的声明上去声明抛出异常,多个异常类名中间用逗号分隔
*/
public class ExceptionDemo02 {
public static void main(String[] args) throws Exception {
division(50,0);
}
public static int division(int a, int b) throws Exception{
return a / b;
}
}
结果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at ExceptionDemo02.division(ExceptionDemo02.java:10)
at ExceptionDemo02.main(ExceptionDemo02.java:7)
main方法使用try…catch对异常进行处理
/**
* @Author: TSCCG
* @Date: 2021/06/13 11:14
* throws关键字
* 用来在方法的声明上去声明抛出异常,多个异常类名中间用逗号分隔
*/
public class ExceptionDemo02 {
public static void main(String[] args){
try {
division(50,0);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("后续语句");
}
public static int division(int a, int b) throws Exception{
return a / b;
}
}
结果:
java.lang.ArithmeticException: / by zero
at ExceptionDemo02.division(ExceptionDemo02.java:15)
at ExceptionDemo02.main(ExceptionDemo02.java:8)
后续语句
可见,throws就是将异常不断地上抛,在将异常上抛后,不会执行后面的语句
4. 异常对象的常用方法
异常对象有两个非常重要的方法:
方法名 | 作用 |
---|---|
getMessage() | 获取异常简单的描述信息 |
printStackTrace() | 打印异常追踪的堆栈信息(一般用这个) |
实例演示:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
/**
* @Author: TSCCG
* @Date: 2021/06/13 20:11
*/
public class ExceptionDemo05 {
public static void main(String[] args) {
try {
m1();
} catch (FileNotFoundException e) {
//打印异常信息是必须的,不然很难知道出现的错误
System.out.println("---------获取异常简单描述信息---------");
System.out.println(e.getMessage());
System.out.println("---------打印异常追踪的堆栈信息---------");
e.printStackTrace();
}
}
private static void m1() throws FileNotFoundException {
m2();
}
private static void m2() throws FileNotFoundException {
m3();
}
private static void m3() throws FileNotFoundException {
new FileInputStream("D:\\study\\实验系统网址密码.TX");
}
}
结果:
---------获取异常简单描述信息---------
D:\study\实验系统网址密码.TX (系统找不到指定的文件。)
---------打印异常追踪的堆栈信息---------
java.io.FileNotFoundException: D:\study\实验系统网址密码.TX (系统找不到指定的文件。)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.io.FileInputStream.<init>(FileInputStream.java:93)
at ExceptionDemo05.m3(ExceptionDemo05.java:29)
at ExceptionDemo05.m2(ExceptionDemo05.java:25)
at ExceptionDemo05.m1(ExceptionDemo05.java:21)
at ExceptionDemo05.main(ExceptionDemo05.java:11)
如何查看异常信息?
在打印出的异常追踪堆栈信息中,我们只需要看我们自己写的部分即可
如上面打印的结果中,从at ExceptionDemo05.m3(ExceptionDemo05.java:29)这行开始出现我们自己写的东西,那么就从这行开始,从上往下看。
第29行的代码出现异常,导致第25行也出现异常
第25行出现异常,导致21行也异常
第21行异常,导致11行异常
从而可得知,第29行代码是异常的根源,只需要修改第29行代码即可解决异常
5. 自定义异常
SUN公司提供的JDK内置异常是不够用的。在实际开发中,有很多业务,出现异常后,很多JDK中是没有的。这时,我们可以自定义异常类。
5.1 如何自定义异常类
查看异常类源码得知,异常类的结构都是继承一个异常父类,由一个无参构造方法和一个带有String参数的构造方法组成的。
故我们自定义异常的步骤有两步:
- 编写一个类继承Exception或RuntimeException
- 提供来年规格构造方法,一个无参数,一个带有String参数
public class MyException extends Exception{
public MyException() {
}
public MyException(String s) {
super(s);
}
}
调用自定义异常类:
public class ExceptionDemo10 {
public static void main(String[] args) {
MyException e = new MyException("异常信息");
System.out.println(e.getMessage());
e.printStackTrace();
}
}
结果:
异常信息
MyException: 异常信息
at ExceptionDemo10.main(ExceptionDemo10.java:8)