一.异常
1.1 什么是异常?
- 指程序在执行的过程中,出现的非正常情况,如果不处理最终会导致JVM的非正常停止。
- **强调:**异常不等于语法错误,逻辑错误。因为语法错误,程序都不能运行,逻辑错误,只是结果有误。
1.2 异常的抛出机制
Java中把不同的异常用不同的类表示,一旦发生某种异常,就'创建该异常类型的对象',并且抛出(throw) 。然后程序员可以捕获(catch)到这个异常对象,并处理;如果没有捕获(catch)这个异常对象,那么这个异常对象将会导致程序终止。
1.3 如何对待异常
对于程序出现的异常,一般有两种解决方法:
- 遇到错误就终止程序的运行。
- 程序员在编写程序时,就充分考虑到各种可能发生的异常和错误,极力预防和避免。实在无法避免的,要编写相应的代码进行异常的检测、以及'异常的处理",保证代码的、健壮性。
1.4 异常的分类
- 编译时期异常( 受检异常(执行javac.exe命令时)) :在代码编译阶段,编译器就能明确警示当前代码可能发生 xx异常,并明确提醒程序员提前编写处理它的代码。如果程序员没有编写对应的异常处理代码,则编译器就会直接判定编译失败,从而不能生成字节码文件。通常,这类异常的发生不是由程序员的代码引起的,或者不是靠加简单判断就可以避免的,例如: FileNotFoundException (文件找不到异常)。
- 运行时期异常(非受检异常(执行java.exe命令时)) : 在代码编译阶段,编译器完全不做任何检查,无论该异常是否会发生,编译器都不给出任何提示。**只有等代码运行起来并确实发生了xx异常,**它才能被发现。通常,这类异常是由程序员的代码编写不当引起的,只要稍加判断,或者细心检查就可以避免。
1.5 常见异常举例
Throwable
类有两个主要子类:Error
和 Exception
。
可能常见的Error:
- OutOfMemoryError:当Java虚拟机无法分配对象时,会抛出该错误。通常发生在内存不足的情况下。
- StackOverflowError:当栈溢出,通常是由于递归调用导致的,抛出该错误。
可能常见的Exception:
- ArrayIndexOutOfBoundException
- NullPointerException
- ClassCastException
- NumberFormatException
- InputMismatchException
- ArithmeticException
实例
编译时异常:
java
/*
ClassNotFoundException 类找不到异常
* */
public void test6() {
Class clz = Class.forName("java.lang.String");
}
/*
* FileNotFoundException 文件找不到异常
* IOException 处理输入输出时异常
* */
public void test8() {
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file);
int date = fis.read();
while(date != -1){
System.out.println((char)date);
date = fis.read();
}
fis.close();
}
提示:
运行时异常:
java
import jdk.incubator.vector.VectorOperators;
import java.util.Scanner;
public class ExceptionTest {
/*
* ArrayIndexOutOfBoundsException
* 当访问数组时,使用了非法的索引值(负数或超过数组长度)。
* */
public void test() {
int[] arr = {1, 2, 3};
System.out.println(arr[3]);
//非法索引3,数组最大索引是2
}
/*
* NullPointerException
* 当尝试调用对象的成员变量或方法,而该对象为 null 时抛出。
* */
public void test1() {
String str = null;
System.out.println(str.length());
}
/*
* NumberFormatException
* 当尝试将字符串转换为数字格式,但字符串格式不正确时抛出。
* */
public void test2() {
String invalidNumber = "abc";
int num = Integer.parseInt(invalidNumber);
}
/*
* InputMismatchException
* 需要输入的数据类型与预期的数据类型不匹配时被抛出
* */
public void test3() {
Scanner scanner = new Scanner(System.in);
int num = scanner.nextInt();
System.out.println(num);
}
/*
* ClassCastException
* 在对象强制类型转换时,如果将对象转换为不兼容的类型,会抛出此异常。
* */
public void test4() {
Object obj = "Hello";
Integer num = (Integer) obj;
// 字符串不能转换为整数,会抛出 ClassCastException
}
/*
* ArithmeticException
* 当进行非法的算术运算时,比如整数除以0。
* */
public void test5() {
int a = 10;
int b = 0;
System.out.println(a / b);
}
}
二、异常的处理
Java的异常处理机制通过五个关键字来实现
2.1 try+catch+finally
try
: 包含可能会抛出异常的代码。catch
: 捕获并处理在try
块中抛出的异常。finally
: 包含无论是否抛出异常都必须执行的代码(通常用于资源释放)。
2.1.1 try+catch
1.模型讲解
过程①:"抛"
- 程序在执行的过程当中,一旦出现异常,就会在出现异常的代码处,生成对应异常类的对象,并将此对象抛出。一旦抛出,此程序就不执行其后的代码了。
过程②:"抓"
- 针对于过程1中抛出的异常对象,进行捕获处理。此捕获处理的过程,就称为抓。
- 一旦将异常进行了处理,代码就可以继续执行。
基本结构:
try{
...... //可能产生异常的代码
}
catch(异常类型1 e ){
...... //当产生异常类型1型异常时的处置措施}
catch(异常类型2 e ){
...... //当产生异常类型2型异常时的处置措施
}
finally{
...... //无论是否发生异常,都无条件执行的语句
以下代码可继续执行
......
举例:
java
import java.util.InputMismatchException;
import java.util.Scanner;
public class ExceptionTest {
public static void main(String[] args) {
try {
Scanner scanner = new Scanner(System.in);
int num = scanner.nextInt();
System.out.println("输入的数字为: " + num);
}catch (NullPointerException e){
System.out.println("出现了NullPointerException的异常");
}catch (InputMismatchException e){
System.out.println("出现了InputMismatchException的异常");
}catch (RuntimeException e) {
System.out.println("出现了RuntimeException的异常");
}
System.out.println("异常处理结束,代码可以继续运行");
}
}
结果:
2.注意事项
- 将可能出现异常的代码声明在try语句中。若发生异常,会自动生成对应异常类的对象并抛出。随后,
catch
语句匹配该异常,若匹配成功,则进入catch
块处理异常,之后代码继续执行。 - 如果声明多个
catch
结构且异常类型不存在父子关系,它们的声明顺序不影响。然而,若存在父子关系,子类异常必须声明在父类异常之前,否则,报错。 - catch中异常处理的方式:
- 自己编写输出的语句。
- printStackTrace(): 打印异常的详细信息。(推荐 )
- getMessage(): 获取发生异常的原因。
- try中声明的变量,出了try结构之后,就不可以进行调用了。
2.1.1 finally
1. finally的理解:
- 将一定要被执行的代码声明在finally结构中。
- 无论try中 或catch中是否存在仍未被处理的异常,无论try中 或catch中是否存在return语句等,finally中声明的语句都一定要被执行。
2. 什么样的代码我们一定要声明在finally中呢?
- 我们在开发中,有一些资源(比如:输入流、输出流,数据库连接、Socket连接等资源),在使用完以后,必须显式的进行关闭操作,否则,GC不会自动的回收这些资源。进而导致内存的泄漏。
- 为了保证这些资源在使用完以后,不管是否出现了未被处理的异常的情况下,这些资源能被关闭。我们必须将这些操作声明在finally中!
**注意:**finally语句和catch语句是可选的,但finally不能单独使用。
举例:
java
import java.io.FileReader;
import java.io.IOException;
public class TryCatchFinallyExample {
public static void main(String[] args) {
FileReader reader = null;
try {
// 尝试打开一个文件
reader = new FileReader("test.txt");
// 尝试读取文件中的内容
int data = reader.read();
while (data != -1) {
char character = (char) data;
System.out.print(character);
data = reader.read();
}
} catch (IOException e) {
// 捕获并处理 IOException 异常
System.out.println("发生了 IOException: " + e.getMessage());
} finally {
// 无论是否发生异常,都会执行的清理代码
if (reader != null) {
try {
reader.close(); // 关闭文件流
System.out.println("\n文件已成功关闭。");
} catch (IOException e) {
System.out.println("关闭文件时发生异常: " + e.getMessage());
}
}
}
}
}
3.小知识:
final
:修饰符,用于类(则类不能被继承)、方法(则方法不能被重写) 或 变量(则赋值后不能再被修改)。finally
:用于确保某段代码在异常发生后也能执行,通常用于资源的释放。finalize
:是一个方法,用于执行清理操作(如释放资源),不推荐使用。
2.2 throws+异常类型
throw
: 手动抛出异常。throws
: 用于在方法声明中标识可能抛出的异常。
2.2.1 throws
1. 格式:
public void test() throws 异常类型1,异常处理2,..{
//可能存在编译时异常
}
举例:
java
import java.io.FileReader;
import java.io.FileNotFoundException;
import java.io.IOException;
public class MultiExceptionExample {
// 声明可能抛出多种异常的方法
public static void readFile(String fileName) throws FileNotFoundException, IOException {
FileReader fileReader = new FileReader(fileName);
// 读取文件内容(省略具体实现)
fileReader.close();
}
public static void main(String[] args) {
try {
readFile("nonexistent.txt"); // 文件不存在会抛出异常
} catch (FileNotFoundException e) {
System.out.println("File not found: " + e.getMessage());
} catch (IOException e) {
System.out.println("IO exception occurred: " + e.getMessage());
}
}
}
2.是否真的处理了异常?
- 从编译是否能通过的角度看,看成是给出了异常万一要是出现时候的解决方案。 此方案就是,继续向上抛出(throws)。
- 但是,此throws的方式,仅是将可能出现的异常抛给了此方法的调用者。此调用者仍然需要考虑如何处理相关异常。从这个角度来看,throws的方式不算是真正意义上处理了异常。
3.方法的重写的要求:(针对编译时异常)
- 子类重写的方法抛出的异常类型可以与父类被重写的方法抛出的异常类型相同,或是父类被重写的方法抛出的异常类型的子类。
2.2.2 throw
**throw
**关键字用于手动地抛出一个异常。它可以用来抛出自定义异常或标准异常。
**理解:**程序在执行的过程中,不满足指定条件的情况下,主动使用"throw + 异常类的对象"放十四抛出异常对象。
举例:
java
public class ThrowExample {
public static void main(String[] args) {
try {
checkNumber(-5);
} catch (IllegalArgumentException e) {
System.out.println("捕获到异常: " + e.getMessage());
}
}
static void checkNumber(int number) {
if (number < 0) {
throw new IllegalArgumentException("数字不能为负数: " + number);
}
System.out.println("数字是: " + number);
}
}
**强调:**throw后的代码不能被执行,编译不通过。
2.2.3 注意
throw和throws是一种类似合作的关系。
-
throw
: 用于在方法内部创建一个异常对象并抛出这个异常。(上游排污) -
throws
: 用于在方法签名中声明异常,提醒调用该方法的代码处理这些异常。(下游治污)
2.3 小结
如何选择异常处理的方式?
- 如果程序代码中,涉及到资源的调用(流、 数据库连接、网絡连接等),则必须考虑使用try-catch-finally来处理,保证不出现内存泄漏。
- 如果父类被重写的方法没有throws异常类型,则子类重写的方法中如果出现异常,只能考虑使用try-catch-finally进行处理,不能throws。
- 开发中,方法a中依次调用了方法b,c, d等方法,方法b,c, d之间是递进关系。此时,如果方法b,c,d中有异常,我们通常选择使用throws,而方法a中通常选择使用try-catch- finally。
三、自定义异常
1.如何自定义异常类?
- 继承于现有的异常体系。通常继承于RuntimeException \ Exception
- 通常提供几个重载的构造器
- 提供一个全局常量,声明为: static final Long serialVersionUID;
2.如何使用自定义异常类?
- 在具体的代码中,满足指定条件的情况下,需要手动的使用"throw +自定义异常类的对象"方式,将异常对象抛出。
- 如果自定义异常类是非运行时异常,则必须考虑如何处理此异常类的对象。(具体的: ①try-catch-finally ②throws
举例:
java
public class InvalidAgeException extends Exception {
// 构造函数
public InvalidAgeException(String message) {
super(message); // 调用父类的构造函数
}
}
java
// 自定义异常类
public class User {
private String name;
private int age;
// 设置年龄的方法
public void setAge(int age) throws InvalidAgeException {
if (age < 0 || age > 150) {
throw new InvalidAgeException("年龄必须在0到150之间。");
}
this.age = age; // 设置年龄
}
public int getAge() {
return age; // 返回年龄
}
}
java
public class Main {
public static void main(String[] args) {
User user = new User();
try {
user.setAge(200); // 尝试设置一个无效年龄
} catch (InvalidAgeException e) {
System.out.println("发生异常: " + e.getMessage()); // 处理异常
}
try {
user.setAge(25); // 尝试设置一个有效年龄
System.out.println("用户年龄: " + user.getAge());
} catch (InvalidAgeException e) {
System.out.println("发生异常: " + e.getMessage());
}
}
}
结果:(无效的年龄200,发生了自定义异常,25则正常输出)
3.为什么需要自定义异常类?
见名知意,通过异常的名字直接判断出异常出现的原因,所以在开发中,不满足指定的条件时,指明我们特有的异常类,通过异常类的名称,直接判断出具体出现的问题。