第一章 什么是异常
1.1 异常的概念
异常:指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止
在Java中,异常本身就是一个类,产生异常就是创建一个异常对象并且抛出一个异常对象的过程
Java处理异常的方式是中断处理。
1.2 异常的体系
异常机制其实是帮助我们找到程序中的问题。
异常的根类是:java.lang.Throwable,其有两个子类,分别是
- java.lang.error 错误
- java.lang.exception 异常,平常所说的异常就是exception
Throwable体系:
- Error:错误,不需要处理
- Exception: 需要进行处理的异常
- 异常处理的方式:
- throws 抛出异常
- try…catch 捕获异常
1.3 异常的分类
- Exception分为两大类:
- 运行时异常 [即程序运行时,发生的异常]。
- 编译时异常 [即编程时编译器检查出的异常,Checked异常]。
所有的RuntimeException类及其子类的实例被称为Runtime异常;
不是RuntimeException类及其子类的异常实例则被称为Checked异常。
- 案例
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Demo01_exception {
/*
Exception: 异常
分为运行时异常和非运行时异常
RuntimeException:运行期异常---编译期不检查,运行时出现问题,需要我们回来修改代码
非RuntimeException:非运行期异常---编译器必须处理,否则程序编译不通过,无法运行
Error:错误,不需要处理
*/
//异常/错误案例
public static void main(String[] args) throws ParseException {
show1();//抛出异常
show2();//捕获异常
show3();//程序错误
}
private static void show3() throws ParseException {
//异常1:编译其异常---抛出异常
//格式化日期
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
//把字符串格式的日期转换成Date格式的日期
Date date = format.parse("2022-12-12");
System.out.println(date);
}
private static void show2() {
//异常2:运行期异常---捕获异常
int[] arr1 = {1, 2, 3};
try {
//可能会出现异常的代码
System.out.println(arr1[3]);
} catch (Exception e) {
//异常的处理逻辑
System.out.println(e);
}
}
private static void show1() {
//错误:内存溢出,出现错误:java.lang.OutOfMemoryError
int[] arr2 = new int[1000 * 100000000];
System.out.println(arr2);
}
}
- 抛出异常
- 捕获异常
- 程序错误
1.4 异常产生的过程
第二章 异常的处理
Java异常的处理包含五个关键字:try、catch、finally、throw、throws
2.1 抛出异常throw
throw关键字:
- 作用:使用throw关键字在指定的方法中抛出指定的异常
- 语法:throw new XXXException(“异常产生的原因”)
注意事项:
- throw关键字必须写在方法的内部、
- throw关键字后面new的对象必须是Exception或者是其子类对象
- throw关键字抛出指定的异常对象,我们必须处理
- throw关键字后面new的是RuntimeException或者RuntimeException对象的子类,可以不做处理,交予JVM处理,打印异常,中断程序
- throw关键字后面new的是编译检查异常,那必须处理这个异常,方式:throws或者try…catch
案例:
public class Demo03_Throw {
/*
throw关键字的使用
案例:获取指定数组下标处的元素
1、定义方法
2、参数:int[] arr, int index
3、对传递给方法的参数进行校验合法性
4、如果参数不合法,使用抛出异常的方式,告知调用者,参数传递错误
*/
public static void main(String[] args) {
//1、创建数组
int[] arr1 = null;
int[] arr2 = {1, 2, 3};
//2、赋值调用方法
//参数(arr1,1)
int element1 = getELement(arr1, 1);
System.out.println(element1);
//参数(arr2,2)
int element2 = getELement(arr2, 2);
System.out.println(element2);
}
//定义获取数组元素的方法
private static int getELement(int[] arr, int index) {
//1、判断传递的参数arr是否为null
if (arr == null) {
//空指针异常属于运行期异常,交给JVM处理,我们不需要处理
throw new NullPointerException("传递的数组的值是空");
}
//2、判断index是否在数组的范围内
if (index < 0 || index > arr.length - 1) {
//抛出数组下标越界异常
throw new IndexOutOfBoundsException("传递的下标越界");
}
int ele = arr[index];
//方法的返回值
return ele;
}
}
2.2 声明异常throws
throws关键字:
- 作用:运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常
- 语法格式:修饰符 返回值类型 方法名(参数类型 参数名) throws 异常类型{ … }
- 注意:如果方法中throw抛出了编译时异常,而没有捕获处理,那么就必须通过throws声明,交给调用者处理
案例:
import java.io.FileNotFoundException;
import java.io.IOException;
public class Demo04_Throws {
/*
throws关键字
概念:异常处理的第一种方式:交给别人处理
作用:可以使用throws关键字处理异常对象,会把异常对象声明抛出给方法的调用者处理,
自己不处理,别人处理,最终交给JVM处理---中断程序
格式:在方法声明时使用
注意事项:
1、必须写在方法声明处
2、throws关键字后面的异常必须是 Exception 或者是Exception的子类
3、方法内部如果抛出多个异常对象,那么throws后边必须声明多个异常
4、方发声明处可以使用Exception一个类,代替多个其子类异常
5、throws抛出异常的两种处理方式
a、抛出的异常交给方法调用者处理,最终交给JVM处理
b、try...catch处理异常
*/
//声明异常类型存在子父类关系,直接写父类即可
//public static void main(String[] args) throws FileNotFoundException, IOException {
//readFile("d:\\1.txt");
//}
public static void main(String[] args) throws IOException {
readFile("d:\\1.txt");
}
/*
定义方法:对传递的文件路径进行合法性判断
如果路径不是d:\\1.txt,那么就抛出文件找不到异常,抛给方法的调用者
*/
public static void readFile(String fileName) throws FileNotFoundException, IOException {
//判断传入的文件名称是否是以.txt结尾的
if (!fileName.endsWith(".txt")) {
//抛出IO异常,编译检查型异常,需要throws抛出给方法的调用者或者try...catch捕获处理
throw new IOException("文件后缀名错误");
}
//判断传入文件名称是否一致
if (!fileName.equals("d:\\1.txt")) {
//此异常是编译检查异常,需要处理异常,可以使用throws或者try...catch
throw new FileNotFoundException("文件找不到");
}
//文件名称一致,上传成功
System.out.println("路径正常,读取文件成功");
}
}
2.3 捕获异常try…catch
如果异常出现,会立即终止程序,所以需要我们处理异常,
-
1、该方法不处理,而是声明抛出,由该方法的调用者来处理 ( throws )
-
2、在方法中使用try-catch的语句来处理异常
try-catch的方式就是用于捕获异常
-
作用:在Java中针对性的对某些语句进行捕获,可以对出现的异常进行指定方式的处理
-
语法:
-
try { 编写可能出现异常的代码; }catch(Exception e){ 处理异常的代码; 打印异常、继续抛出异常; }
案例:
import java.io.IOException;
public class Demo05_TryCatch {
/*
try-catch:异常处理的第二种方式:捕获异常自行处理
作用:捕获产生的异常并处理
语法:
try{
可能出现异常的代码;
}catch(Exception e){
异常处理的逻辑代码;
记录日志,打印异常,继续抛出异常;
}
注意事项:
1、try中可能抛出多个异常对象,那么可以使用多个catch捕获处理异常
2、执行顺序:
如果try产生异常,try---catch---继续执行catch之后的代码
如果try没有差生异常,就不会执行catch中的代码,继续执行catch之后的代码
*/
public static void main(String[] args) {
//调用方法,并捕获处理异常
try {
//产生异常的代码
readFule("d:\\1.tx");
}catch (IOException e){
//异常的处理逻辑
System.out.println("文件名称后缀不匹配!!!");
}
System.out.println("后续执行的代码");
}
/*
需求:
1、定义读取文件的方法
2、判断传递的文件名称后缀是否以.txt结尾
3、不匹配抛出异常给方法的调用者,告知文件后缀名称不匹配
*/
//定义方法
public static void readFule(String fileName) throws IOException {
if (!fileName.endsWith(".txt")){
throw new IOException("出现异常,后缀不匹配!!!");
}
System.out.println("文件名匹配成功!!!");
}
}
- Throwable定义了获取异常信息的三个方法
- 1、String getMessage(); 返回此Throwable的简短描述
- 2、String toString(); 返回此Throwable的详细描述
- 3、void printStackTrace(); JVM打印异常对象,默认调用此方法
import java.io.IOException;
public class Demo06_ThrowableMethods {
/*
Throwable定义了三个处理异常的方法
1、String getMessage();
返回此Throwable的简短描述
2、String toString();
返回此Throwable的详细描述
3、void printStackTrace();
JVM打印异常对象,默认调用此方法
*/
public static void main(String[] args) {
try {
//出现异常的代码
readFile("d:\\1.tx");
} catch (IOException e) {
System.out.println("文件名称不匹配!!!");
/*
1、String getMessage();
返回此Throwable的简短描述
2、String toString();
返回此Throwable的详细描述
3、void printStackTrace();
JVM打印异常对象,默认调用此方法
*/
System.out.println("简短的信息描述是:" + e.getMessage());
System.out.println("详细的信息描述是:" + e.toString());
e.printStackTrace();
}
System.out.println("后续的代码");
}
private static void readFile(String fileName) throws IOException {
if (!fileName.endsWith(".txt")) {
throw new IOException("验证失败,不匹配");
}
System.out.println("文件后缀名匹配成功!!!");
}
}
2.4 finally 代码块
finally:有一些特定的代码。无论异常发生与否,都需要执行。同时,因为异常导致程序跳转,导致有些代码无法被执行到,而finally就是解决这个问题的,在finally中存放的代码无论无核是会被执行的
- 什么时候的代码必须最终执行
- 当在try语句块中执行了一些物理的如:磁盘文件,网络连接,数据库连接等之后,我们必须在使用完毕之后关闭资源,所以finally此时配上了用处
finally语法:
try {
//可能出现异常的代码
}catch (Exception e){
//异常的处理逻辑以及错误信息的处理
}finally {
//释放资源
}
finally的注意事项:
- finally并且不能单独使用
- finally一般用于资源释放,无论程序是否出现异常,最终都要释放资源
案例:
import java.io.IOException;
public class Demo07_Finally {
/*
finally的使用:
不管是否出现异常,都会被执行
不能单独使用
一般用于关闭或者释放资源
*/
public static void main(String[] args) {
try {
//可能出现异常的代码
readFile("d:\\1.txt");
} catch (IOException e) {
//异常处理逻辑
e.printStackTrace();
} finally {
//无论是否出现异常都会被执行
System.out.println("释放资源");
}
}
private static void readFile(String fileName) throws IOException {
if (!fileName.endsWith(".txt")) {
throw new IOException("验证失败,不匹配");
}
System.out.println("文件后缀名匹配成功!!!");
}
}
2.5 异常注意事项
- 多个异常捕获之后的处理方式
- 1、多个异常分别处理
- 2、多个异常一次捕获,多次处理
- 3、多个异常一次捕获,一次处理
一般使用一次捕获,多次处理的方式,格式如下
try{
可能出现异常的代码
}catch(异常类型A e){
处理异常的代码
//记录日志,打印异常信息,继抛出异常
}catch(异常类型B e){
出现异常的代码
//记录日志,打印异常信息,继抛出异常
}
- 注意事项:
- 这种异常处理方式,要求多个catch中的异常不能相同,并且如果catch中的多个异常之间有父子关系,那么子类异常要求在上面的catch中处理,父类异常要求在下面的catch中处理
- 运行时异常被抛出可以不处理。
- 如果父类抛出多个异常,子类覆盖父类方法时,只能抛出相同的异常或者其他的子类
- 父类方法没有抛出异常,子类覆盖该方法不可能抛出异常,子类只能捕获处理
- 在try-catch后面追加finally代码,其中的代码一定会执行,通常用于资源的释放
- 如果finally中有return语句,永远返回finally中的结果,但是要避免这种情况的发生
案例:方式一:多个异常分别处理
import java.util.ArrayList;
import java.util.List;
public class Demo8_MoreCatch {
/*
多个异常的捕获方式
1、多个异常分别处理
2、多个异常一次捕获,多次处理
3、多个异常一次捕获,一次处理
*/
public static void main(String[] args) {
//方式一:多个异常分别处理
try {
int[] arr = {1, 2, 3};
//ArrayIndexOutOfBoundsException
System.out.println(arr[3]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println(e);
}
try {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
//IndexOutOfBoundsException
System.out.println(list.get(3));
} catch (IndexOutOfBoundsException e) {
System.out.println(e);
}
}
}
案例:方式二:多个异常一次捕获多次处理
import java.util.ArrayList;
import java.util.List;
public class Demo8_MoreCatch {
/*
多个异常的捕获方式
1、多个异常分别处理
2、多个异常一次捕获,多次处理
3、多个异常一次捕获,一次处理
*/
public static void main(String[] args) {
/*
方式二:多个异常一次捕获多次处理
注意事项:
catch里面定义的异常变量,如果有子父类观系,
那么子类的异常变量必须写在上面,父类的写在下面
*/
try {
//数组下标越界
int[] arr = {1, 2, 3};
//ArrayIndexOutOfBoundsException
System.out.println(arr[3]);
//下标越界
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
//IndexOutOfBoundsException
System.out.println(list.get(3));
} catch (ArrayIndexOutOfBoundsException e) {//子类 在上
System.out.println(e);
} catch (IndexOutOfBoundsException e) {//父类 在下
System.out.println(e);
}
System.out.println("后续代码");
}
}
案例:方式三:多个异常多个异常一次捕获,一次处理
import java.util.ArrayList;
import java.util.List;
public class Demo8_MoreCatch {
/*
多个异常的捕获方式
1、多个异常分别处理
2、多个异常一次捕获,多次处理
3、多个异常一次捕获,一次处理
*/
public static void main(String[] args) {
/*
方式二:多个异常一次捕获,一次处理
*/
try {
//数组下标越界
int[] arr = {1, 2, 3};
//ArrayIndexOutOfBoundsException
System.out.println(arr[3]);
//下标越界
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
//IndexOutOfBoundsException
System.out.println(list.get(3));
} catch (IndexOutOfBoundsException e) {
System.out.println(e);
}
System.out.println("后续代码");
}
}
第三章 自定义异常
3.1 自定义异常概述
什么是自定义异常:
- 在Java开发的过程中,会有一些异常是JDK未定义的,此时我们需要根据业务的异常情况来自定义异常
如何自定义异常:
- 自定义一个编译期异常,自定义类,并且继承java.lang.Exception
- 自定义一个运行期异常,自定义类,并且继承java.lang.RuntimeException
3.2 自定义异常案例
- 需求:
- 创建自定义异常类
public class Demo09_RegisterException extends Exception{
/*
自定义异常:
语法格式:
public class XXXException extends Exception/RuntimeException {
添加一个空参数的构造方法
添加一个带异常信息的构造方法
}
注意:
1、自定义异常类一般都是以Exception结尾,说明该类是一个异常类
2、自定义异常类:必须继承Exception或RuntimeException
a、继承Exception,那么定义的异常类是以个编译期异常类,
如果方法内部抛出了编译期异常,那么必须处理这个异常
要么抛出异常,要么try-catch捕获异常
b、继承RuntimeException。那么自定义的异常类就是运行期异常
运行期异常无需处理,交给JVM虚拟机处理---终端处理
*/
//添加无参构造
public Demo09_RegisterException() {
super();
}
/*
添加一个带异常信息的构造方法
*/
public Demo09_RegisterException(String message) {
super(message);
}
}
- 创建测试类
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
public class Demo09_TestRegister {
/*
需求:模拟注册操作,如果用户名存在,则抛出异常,提示用户名已存在
步骤:
1、使用集合保存已经注册的用户名信息
2、使用Scanner获取用户输入的注册的用户名信息
3、定义一个方法,对用户输入的用户名进行判断
4、遍历集合中存储的已经注册的用户名
5、使用输入的用户名和遍历的用户名进行比对
6、如果是true。说明用户名已经存在,抛出自定义异常,告知用户名已经存在
7、如果是false。继续遍历
8、如果循环比对结束了,还没有找到重复的用户名,提示用户注册成功
*/
//1、定义集合存放已经注册的用户名
public static List<String> list = new ArrayList<>();
public static void main(String[] args) throws Demo09_RegisterException {
list.add("tom");
list.add("tony");
list.add("jack");
list.add("anny");
for (int i = 0; i < 5; i++) {
//2、使用Scanner获取用户输入的用户名
System.out.println("请输入您要注册的用户名");
String username = new Scanner(System.in).nextLine();
//3。调用方法
checkArray(username);
//4、打印集合
System.out.println(list);
}
}
//3、定义方法---抛出异常
public static void checkArray(String name) throws Demo09_RegisterException {
//遍历集合中已经存在的用户名
for (String item : list) {
if (item.equals(name)) {
//true:说明用户名已经存在,抛出自定义异常,告知用户名已经存在
throw new Demo09_RegisterException("用户名已经存在,请重新注册");
}
}
//如果循环比对结束了,还没有找到重复的用户名,提示用户注册成功
System.out.println("恭喜您注册成功");
//添加用户名到集合中
list.add(name);
}
}
- 运行结果