一、异常引入
1.1 两数相除
public class Demo1 {
public static void main(String[] args) {
int i = 10;
Scanner sc = new Scanner(System.in);
int i1 = sc.nextInt(); // 当录入一个非int类型时:java.util.InputMismatchException
System.out.println(i / i1);
// System.out.println(i / 0); // java.lang.ArithmeticException: / by zero
}
}
-
可能会出现的异常
-
当录入的数据应为
int
类型,但是录入非int类型数据的时候,出现异常InputMismatchException
-
除数为0的时候,出现异常:
ArithmeticException
-
-
异常
Exception
:在程序的运行过程中,发生了不正常的现象,阻止了程序的运行,我们称之为异常
1.2 处理方式一:if-else
-
使用
hasNextInt
判断录入的是否为整数,不是则打印提示 -
加上
if
判断除数是否为0,若除数为0,给出提示 -
代码演示
-
public class Demo1 { public static void main(String[] args) { int i = 10; Scanner sc = new Scanner(System.in); int i1 = sc.nextInt(); // 当录入一个非int类型时:java.util.InputMismatchException System.out.println(i / i1); System.out.println(i / 0); // java.lang.ArithmeticException: / by zero } }
-
缺点:
- 代码臃肿,业务代码和处理异常的代码混合在一起
- 可读性差
- 程序员需要花费大量精力来维护这个漏洞
- 程序员很难解决所有的异常
1.3 处理方式二:try…catch…finally
- java提供的异常处理机制
try...catch...finally
- 代码演示
public class Demo1 {
public static void main(String[] args) {
int i = 10;
Scanner sc = new Scanner(System.in);
try {
int i1 = sc.nextInt();
System.out.println(i / i1);
}catch (Exception e){
e.printStackTrace();
}
}
}
二、 异常概述
2.1 异常的体系结构
-
异常的概述
- 就是程序出现了不正常的情况。程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。
-
异常的体系结构
Error
:严重问题,通过代码无法处理。比如内存溢出Exception
:称为异常类,表示程序本身可以处理的问题RuntimeException及其子类
:运行时异常。例如空指针异常,数组索引越界异常除RuntimeException之外所有的异常
:编译期必须处理的,否则程序不能通过编译。例如日期格式化异常
2.2 编译时异常和运行时异常的区别
-
编译时异常
- 都是
Exception
类及其子类 - 必须显示处理,否则程序就会发生错误,无法通过编译
- 都是
-
运行时异常
- 都是
RuntimeException
类及其子类 - 无需显示处理,也可以和编译时异常一样处理
- 都是
-
图示
2.3 JVM处理异常的方式
- 异常处理流程
- 首先会看,程序中有没有自己处理异常的代码
- 如果没有,交给本方法的调用者处理(向上传递)
- 如果调用者也没有处理,最终这个异常会交给JVM虚拟机默认处理
- JVM 默认处理的两个步骤:
- 把异常的名称,错误原因及异常出现的位置等信息输出在了控制台
- 程序停止执行
2.4 查看异常信息
- 控制台在打印异常信息:
- 打印异常类名
- 异常出现的原因
- 异常出现的位置
- 我们调bug时,可以根据提示,找到异常出现的位置、分析原因、修改异常代码
三、几种异常的处理方式
3.1 throws方式处理异常
-
定义格式
public void 方法() throws 异常类名 { }
-
注意事项
- 这个throws格式是跟在方法的括号后面的
- 编译时异常必须要进行处理,两种处理方案:
try...catch …finally
或者throws
,如果采用throws
这种方案,在方法上进行显示声明,将来谁调用这个方法谁处理 - 运行时异常因为在运行时才会发生,所以在方法后面可以不写,运行时出现异常默认交给
jvm
处理
- 运行时异常报错
3.2 throw抛出异常
-
格式:
throw new 异常();
-
注意
这个格式是在方法内的,表示当前代码手动抛出一个异常,下面的代码不用再执行了
-
throws
和throw
的区别throws throw 用在方法声明后面,跟的是异常类名 用在方法体内,跟的是异常对象名 表示声明异常,调用该方法有可能会出现这样的异常 表示手动抛出异常对象,由方法体内的语句处理 -
还是要调用者处理,不常单独用
3.3 try-catch方式处理异常
-
定义格式
try { // 可能出现异常的代码; } catch(异常类名 变量名) { // 异常的处理代码; }
-
执行流程
- 把可能出现异常的代码放入
try
代码块中, - 若出现异常,底层会将异常封装为对象,被
catch
后面()
中的那个异常对象接收,执行对应catch
代码块里的内容;(try
中出现异常那行后续的代码不执行) - 若无异常或异常不匹配,
catch
无法捕获,则catch
代码块内容不执行 - 无论是否出现异常,
finally
中的代码块都一定会执行 - 异常处理结束后,后面的逻辑代码正常执行
- 把可能出现异常的代码放入
-
示例代码
public class Demo03 {
public static void main(String[] args) {
System.out.println("开始");
method();
System.out.println("结束");
}
public static void method() {
try {
int[] arr = {1, 2, 3};
System.out.println(arr[3]); // 发现异常,直接执行catch块内语句
System.out.println("这里访问不到");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("你访问的数组索引不存在,请回去修改为正确的索引");
}
}
}
- 一些问题
-
如果 try 中没有遇到问题,怎么执行?
- 会把try中所有的代码全部执行完毕,不会执行catch里面的代码
-
如果 try 中遇到了问题,那么 try 下面的代码还会执行吗?
- 那么直接跳转到对应的catch语句中,try下面的代码就不会再执行了
当catch里面的语句全部执行完毕,表示整个体系全部执行完全,继续执行下面的代码
- 那么直接跳转到对应的catch语句中,try下面的代码就不会再执行了
-
如果出现的问题没有被捕获,那么程序如何运行?
- 那么
try...catch
就相当于没有写.那么也就是自己没有处理。
默认交给虚拟机处理。
- 那么
-
同时有可能出现多个异常怎么处理?
- 出现多个异常,那么就写多个
catch
就可以了.
注:如果多个异常之间存在子父类关系.那么父类一定要写在下面
- 出现多个异常,那么就写多个
3.4 多重catch
try
中出现异常以后,将异常的类型跟catch
后面的类型依次比较,执行第一个与异常类型匹配的catch语句- 一旦执行其中一条
catch
语句以后,后面的catch
语句就会被忽略了。所以一般将Exception异常放在最后,用于捕获未知的异常 - 用于对不同的异常分别捕获,使用不同的处理方法。
- 在
JDK1.7
以后,异常的新处理方式:可以并列用|
符号连接 - 代码示例
public class Demo4 {
public static void main(String[] args) {
int i = 10;
Scanner sc = new Scanner(System.in);
try {
int i1 = sc.nextInt();
System.out.println(i / i1);
}catch (InputMismatchException e){ // 多重catch
e.printStackTrace();
}catch (ArithmeticException e){
e.printStackTrace();
}catch (Exception e){ // 父类写在后面
e.printStackTrace();
}
}
}
3.5 Throwable成员方法
- 在捕获异常
catch
后的几种常用处理方法:
方法名 | 说明 |
---|---|
public String getMessage() | 返回此 throwable 的详细消息字符串 |
public String toString() | 返回此可抛出的简短描述 |
public void printStackTrace() | 把异常的错误信息输出在控制台 |
- 示例代码
public class Demo05 {
public static void main(String[] args) {
System.out.println("开始");
method();
System.out.println("结束");
}
public static void method() {
try {
int[] arr = {1, 2, 3};
System.out.println(arr[3]); // 发现异常,直接执行catch块内语句
System.out.println("这里访问不到");
} catch (ArrayIndexOutOfBoundsException e) {
String message = e.getMessage();
System.out.println(message);
String s = e.toString();
System.out.println(s);
e.printStackTrace(); // 不中断程序的运行
}
}
}
3.6 finally的使用
3.6.1 finally介绍
-
引入
- 异常未被捕获、异常被主动
throw
抛出或try
中遇到return
都会导致try...catch
后面的代码不执行
- 异常未被捕获、异常被主动
-
如何一定执行后面的代码?
- 将后面的代码用
finally{}
代码块包裹起来,则一定会执行 - 只有
System.exit(0)
会使finally
中的代码不执行
- 将后面的代码用
-
示例代码
public class Demo6 {
public static void main(String[] args) {
try {
System.out.println("try...");
// System.exit(0); 强制退出虚拟机,finally...不打印
return;
}catch (Exception e){
}finally {
System.out.println("finally...");
}
System.out.println("后续代码"); // 代码不运行
}
}
return
和finally
的执行顺序(☆)- 先执行
finally
后直接return
- 先执行
3.6.2 什么样的代码会放在finally中呢?
- 关闭数据库资源
- 关闭IO流资源
- 关闭socket资源
- 等需要系统调用的资源
3.7 异常的练习 (应用)
-
需求
键盘录入学生的姓名和年龄,其中年龄为18 - 25岁,超出这个范围是异常数据不能赋值.需要重新录入,一直录到正确为止
-
实现步骤
- 创建学生对象
- 键盘录入姓名和年龄,并赋值给学生对象
- 如果是非法数据就再次录入
-
代码实现
学生类
public class Student {
private int age;
private String name;
public Student() {
}
public Student(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age >= 18 && age <= 25) {
this.age = age;
} else {
throw new RuntimeException("年龄不合法");
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
- 测试类
import java.util.InputMismatchException;
import java.util.Scanner;
public class TestStudent {
public static void main(String[] args) {
Student student = new Student();
Scanner sc = new Scanner(System.in);
System.out.println("请输入学生的姓名");
String name = sc.next();
student.setName(name);
while (true) {
System.out.println("请输入学生年龄");
int age;
try {
age = sc.nextInt();
} catch (InputMismatchException e) {
System.out.println(e.toString());
System.out.println("请输入一个整数");
sc.nextLine(); // 缓存区还存在非int值,先接受掉
continue;
}
try {
student.setAge(age);
break;
} catch (RuntimeException e) {
System.out.println(e.toString());
System.out.println("请重新输入");
continue;
}
}
String s = student.toString();
System.out.println(s);
}
}
- 运行结果
3.8 自定义异常
-
自定义异常概述
当Java中提供的异常不能满足我们的需求时,我们可以自定义异常
-
实现步骤(继承后提供两个构造方法)
- 定义异常类
- 写继承关系
- 提供空参构造
- 提供带参构造
-
代码实现
异常类
public class AgeOutOfBoundsException extends RuntimeException {
public AgeOutOfBoundsException() {
}
public AgeOutOfBoundsException(String message) {
super(message);
}
}
- 学生类
...
public void setAge(int age) {
if(age >= 18 && age <= 25){
this.age = age;
}else{
//如果Java中提供的异常不能满足我们的需求,我们可以使用自定义的异常
throw new AgeOutOfBoundsException("年龄超出了范围");
}
}
...