一、异常
1、概述
异常 :指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。
在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java处
理异常的方式是中断处理。
2、异常体系
Throwable体系:
-
Error:严重错误Error,无法通过处理的错误,只能事先避免,好比绝症。
-
Exception:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的
Throwable常用方法:
-
public void printStackTrace()
:打印异常的详细信息。包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。
-
public String getMessage()
:获取发生异常的原因。提示给用户的时候,就提示错误原因。
-
public String toString()
:获取异常的类型和异常描述信息(不用)。
3、异常分类
-
编译时期异常: checked异常。在编译时期,就会检查,如果没有处理异常,则编译失败。(如日期格式化异常)
-
运行时期异常: runtime异常。在运行时期,检查异常.在编译时期,运行异常不会被编译器检测(不报错)。(如数学异常)
异常产生图解
二、异常的处理
1、抛出异常throw
格式 : throw new 异常类; 小括号中可以自定义显示信息.
下面演示空指针和索引越界异常
public class ExceptionTest1 {
public static void main(String[] args) {
int[] arr = {10, 20, 30};
// 调用方法
int result = getElement(arr, 2);
System.out.println("result = " + result);
// 后续的代码逻辑 ...
int num1 = 10;
int num2 = 20;
int sum = num1 + num2;
System.out.println("sum = " + sum);
}
// 方法 : 获取执行下标数组中的元素, 并返回
public static int getElement(int[] array, int index) {
// 演示 : 手动抛出异常
// 格式 : throw new 异常对象(自定义信息);
// 判断1. 空引用异常
if (array == null) {
throw new NullPointerException("数组引用不能为空, 请修正!");
}
// 判断2. 下标越界
if (index < 0 || index >= array.length) {
throw new ArrayIndexOutOfBoundsException("数组下标越界, 请修正. " + index);
}
// 正常逻辑 ...
int element = array[index];
return element;
}
}
2、Objects非空判断
如果遇到要判断对象是否为空可以使用
Objects.requireNonNull(array, "数组引用不能为空, 请修正!");
底层实现 :
public static <T> T requireNonNull(T obj, String message) {
if (obj == null)
throw new NullPointerException(message);
return obj;
}
public class Test {
public static void main(String[] args) {
String[] str = null;
Objects.requireNonNull(str,"数组不能为空");
}
}
3、声明异常throws
格式 : 方法名称 throws 异常类型 { }
public class ParseDateTest1 {
public static void main(String[] args) throws ParseException {
Scanner sc = new Scanner(System.in);
System.out.println("亲, 请输入一个日期字符串 (格式:yyyy-MM-dd) :");
String date = sc.nextLine();
sc.close();
// 调用方法
parseDate(date);
System.out.println("程序正常结束完成 ...");
}
// 实现 : 定义一个方法, 接收字符串参数, 并将字符串解析为一个日期对象.
public static void parseDate(String str) throws ParseException {
// 1. 创建一个日期格式化对象
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
// 2. 解析
// 处理方案一 : 继续声明 (继续抛)
// 翻译 : 如果发生了异常, 我不处理, 交给我的调用者处理. ( throws ParseException )
Date date = df.parse(str);
System.out.println("date = " + date);
}
}
4、捕获异常try...catch
格式 :
try {
// 编写可能发生异常的代码
} catch(异常类型 e) {
// 捕获的处理方案 (什么都不写, 也叫处理方案)
}
public class ParseDateTest1 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("亲, 请输入一个日期字符串 (格式:yyyy-MM-dd) :");
String date = sc.nextLine();
sc.close();
// 调用方法
// 处理方案二 : 异常捕获 try - catch ...
try {
parseDate(date);
} catch (ParseException e) {
e.printStackTrace(); // 日志信息 (文件)
}
System.out.println("程序正常结束完成 ...");
}
// 实现 : 定义一个方法, 接收字符串参数, 并将字符串解析为一个日期对象.
public static void parseDate(String str) throws ParseException {
// 1. 创建一个日期格式化对象
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
// 2. 解析
// 处理方案一 : 继续声明 (继续抛)
// 翻译 : 如果发生了异常, 我不处理, 交给我的调用者处理. ( throws ParseException )
Date date = df.parse(str);
System.out.println("date = " + date);
}
}
5、finally 代码块的使用 : try – catch- finally (资源)
使用场景 : IO流读写操作中.finally是用来释放资源的。
格式 :
try {
// 编写可能发生异常的代码
} catch(异常类型 e) {
// 捕获的处理方案 (什么都不写, 也叫处理方案)
} finally {
// 释放资源 (无论程序是否发生异常, 都会执行 finally 代码块)
}
public class FileReaderTest1 {
public static void main(String[] args) {
boolean result = readFile("C:\\Users\\xieco\\Desktop\\day05-异常、线程\\a.txt");
System.out.println("result = " + result);
System.out.println("程序正常执行结束...");
}
// 定义一个方法 : 读取文件, 并返回是否读取成功
public static boolean readFile(String fileName) {
// 处理方案 : try - catch - finally (数据库: 连接对象)
FileReader reader = null;
try {
// 1. 模拟一段代码, 创建一个文件读取对象
// 说明 : reader 与硬盘上的文件关联了, 需要使用完毕后进行关闭
reader = new FileReader(fileName);
// 读写操作中可以发生异常 ...
// try 的最后一条语句返回结果
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
} finally {
// 作用 : 为关闭资源而生.
System.out.println("资源被关闭了...");
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
// ignore 忽略
}
}
}
}
}
6、异常注意事项
注意点一:
1. 如果子类重写父类的方法, 父类该方法没有声明编译时期异常, 子类重写时, 也不能声明编译时期异常.
2. 如果实现类重写接口中的抽象方法, 接口中该方法没有声明编译时期异常, 实现类重写时也不可以声明编译时期异常.
3.如果存在多个 catch, 父类异常类型必须要写在后面. FileNotFoundException, IOException (后面)
注意点二:
如果子类方法中有异常, 怎么办 ??? 子类只能自己 try-catch, 不能向外抛.
// 原因 : 一旦子类向外 `抛出 / 声明` 异常, 那么子类该方法就父类不一致了.
注意点三:
多个catch捕获的注意点 : catch 异常类型中, 子类类型必须在父类类型之前被捕获,因为多态语法为父类引用可以接收子类对象.
public class DetailException {
public static void main(String[] args) {
try {
FileReader reader = new FileReader("");
reader.read();
} catch (FileNotFoundException e) { // 子类
e.printStackTrace();
} catch (IOException e) { // 父类
e.printStackTrace();
}
}
}
三、自定义异常
1、说明 : 程序开发中, 遇到一些特殊的业务逻辑, Java语言没有提供该逻辑对应的异常类, 此时, 就需要程序员自定义异常类实现.
2、异常类定义要求:
1. 自定义一个编译期异常: 自定义类 并继承于java.lang.Exception。
2. 自定义一个运行时期的异常类:自定义类 并继承于java.lang.RuntimeException。
3、场景 : 注册异常, 如果用户名已经被注册过了. 此时, 不能再使用该用户名注册了, 此时, 需要程序抛出一个注册异常进行代码逻辑的提醒. (提前处理)
定义一个异常类:
/*
顶层类 : Throwable
异常类 :
1. Exception (提前处理)
2. RuntimeException (出现问题后在处理)
自定义异常类, 仅需要提供两个构造方法集合. (无参, 有参)
*/
public class RegisterException extends Exception {
public RegisterException() {
super();
}
public RegisterException(String message) {
super(message);
}
}
异常测试类:
public class TestRegisterException {
// 用户名 : 数据库 (使用数组模拟)
private static String[] names = { "Jacker", "Peter", "lily" };
// 测试 : main 方法
public static void main(String[] args) {
String name = "Peters";
// 调用方法
try {
boolean result = checkUsername(name);
System.out.println("result = " + result);
} catch (RegisterException e) {
System.out.println("异常信息为 : " + e.getMessage());
}
}
// 提供检查用户名是否存在的方法
public static boolean checkUsername(String name) throws RegisterException {
// 1. 遍历 names 数组
for (int i = 0; i < names.length; i++) {
// 2. 取出
String username = names[i];
// 3. 判断
if (username.equals(name)) {
// 4. 名字重复了, 使用异常提醒
// 注意点 : 编译时期异常. 当前方法必须声明
throw new RegisterException("用户名已经存在, 请重新输入.");
}
}
return true;
}
}