一、异常的概念
Java语言中,异常是程序运行时出现的不正常或意外情况。
二、异常的分类
1. 异常的分类
答:
Java异常都继承自 Throwable
,主要分两类:
- Error:系统严重错误,如
OutOfMemoryError
(堆内存溢出错误),程序无法处理。 - Exception:程序可处理异常,又分为:
- 受检异常:编译时需处理,如
IOException
(输入输出异常)。 - 非受检异常:编译时不强制处理,如
NullPointerException
(空指针异常)。
- 受检异常:编译时需处理,如
2. 受检异常和非受检异常在使用上有什么区别?
答:
- 受检异常继承自
Exception
。使用时:- 要么在方法内部使用
try-catch
捕获处理, - 要么在方法声明处使用
throws
关键字抛出,否则编译不通过。
- 要么在方法内部使用
- 非受检异常继承自
RuntimeException
。使用时:- 不需要在方法声明中显式抛出,
- 也不强制要求在方法内部捕获,编译能正常通过。
3. 常见的Error、受检异常、非受检异常有哪些?
答:
常见 Error
StackOverflowError
:栈溢出错误OutOfMemoryError
:内存溢出错误NoClassDefFoundError
:类定义未找到错误LinkageError
:链接错误
常见受检异常
IOException
:输入输出异常SQLException
:数据库操作异常ClassNotFoundException
:类未找到异常InterruptedException
:线程中断异常
常见非受检异常
NullPointerException
:空指针异常ArrayIndexOutOfBoundsException
:数组越界异常ArithmeticException
:算术异常NumberFormatException
:数字格式异常IllegalArgumentException
:非法参数异常IndexOutOfBoundsException
:索引越界异常
4. 分别给出一个Error、受检异常、非受检异常的简单示例代码。
答:
(1) Error 示例
StackOverflowError
属于 Error
类型,通常在方法无限递归调用时产生,因为每次方法调用都会在栈上分配空间,无限递归会使栈空间耗尽。
// 测试类
public class StackOverflowErrorExample {
public static void recursiveMethod() {
// 无限递归调用自身
recursiveMethod();
}
public static void main(String[] args) {
try {
recursiveMethod();
} catch (StackOverflowError e) {
System.out.println("捕获到 StackOverflowError: " + e);
}
}
}
测试结果:
捕获到 StackOverflowError: java.lang.StackOverflowError
解释:
在 recursiveMethod
方法中,它不断地调用自身,没有终止条件,最终会导致栈空间耗尽,抛出 StackOverflowError
。在 main
方法里,使用 try-catch
块捕获该错误并输出错误信息。
(2) 受检异常示例
IOException
是典型的受检异常,在进行文件操作、网络操作等可能发生输入输出问题的场景中经常出现,使用时必须进行处理。
import java.io.FileReader;
import java.io.IOException;
public class IOExceptionExample {
public static void readFile() throws IOException {
// 尝试打开一个文件进行读取
FileReader reader = new FileReader("nonexistentfile.txt");
int data;
while ((data = reader.read()) != -1) {
System.out.print((char) data);
}
reader.close();
}
public static void main(String[] args) {
try {
readFile();
} catch (IOException e) {
System.out.println("捕获到 IOException: " + e.getMessage());
}
}
}
测试结果:
捕获到 IOException: nonexistentfile.txt (系统找不到指定的文件。)
解释:
readFile
方法尝试打开一个不存在的文件 nonexistentfile.txt
进行读取,这会引发 IOException
。
由于 IOException
是受检异常,readFile
方法使用 throws
关键字声明抛出该异常。
在 main
方法中,调用 readFile
方法并使用 try-catch
块捕获并处理该异常。
(3) 非受检异常示例
NullPointerException
属于非受检异常,通常在尝试对一个 null
对象进行操作时抛出。
public class NullPointerExceptionExample {
public static void main(String[] args) {
String str = null;
try {
// 对 null 对象调用方法,会抛出 NullPointerException
int length = str.length();
System.out.println("字符串长度: " + length);
} catch (NullPointerException e) {
System.out.println("捕获到 NullPointerException: " + e);
}
}
}
测试结果:
捕获到 NullPointerException: java.lang.NullPointerException
解释:
在 main
方法中,定义了一个 null
的字符串对象 str
,然后尝试调用它的 length()
方法,这会导致 NullPointerException
异常。使用 try-catch
块捕获并处理该异常。
三、try-catch-finally
语句
1. 简述 try-catch-finally
语句块中各部分的作用
答:
try
块包含可能抛出异常的代码;catch
块用于捕获并处理try
块中抛出的指定类型异常;finally
块无论是否发生异常都会执行,常用于释放资源。
2. 在 try-catch-finally
结构中,如果 try
块中有 return
语句,finally
块中的代码会执行吗?
答:
会执行。
在 try
块遇到 return
语句时,会先保存返回值,然后执行 finally
块中的代码,最后再返回之前保存的值。
3. catch
块捕获异常后,若 finally
块中有 return
语句,会对 try
或 catch
块中的 return
结果有什么影响?
答:
若 finally
块中有 return
语句,会覆盖 try
或 catch
块中的 return
结果,最终返回 finally
块中 return
的值。
4. 请给出一个经典且简洁的try-catch-finally 语句的使用场景
答:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class TryCatchFinallyExample {
public static void main(String[] args) {
BufferedReader reader = null;
try {
// 尝试打开文件进行读取
reader = new BufferedReader(new FileReader("test.txt"));
String line;
// 逐行读取文件内容并输出
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
// 捕获并处理可能出现的输入输出异常
System.err.println("读取文件时出现错误: " + e.getMessage());
} finally {
try {
if (reader != null) {
// 确保文件读取器被关闭
reader.close();
}
} catch (IOException e) {
// 处理关闭文件读取器时可能出现的异常
System.err.println("关闭文件读取器时出现错误: " + e.getMessage());
}
}
}
}
代码解释
try
块: 代码尝试打开并读取文件test.txt
的内容,将文件内容逐行输出。由于文件操作可能会出现异常,如文件不存在、文件无法访问等,所以这些操作被放在try
块中。catch
块: 当try
块中的代码抛出IOException
时,catch
块会捕获该异常,并将错误信息打印到标准错误输出。finally
块: 无论try
块中是否发生异常,finally
块中的代码都会执行。在这个例子中,finally
块用于关闭文件读取器BufferedReader
,以确保资源被正确释放。关闭操作也可能会抛出IOException
,因此同样使用try-catch
语句来处理。
四、自定义异常
1. 为什么需要自定义异常?
答:
在实际开发中,Java内置的异常类型可能无法精准描述业务场景中出现的问题。
自定义异常能使异常信息更具针对性,便于代码的维护和调试,让开发者可以根据业务需求对特定情况进行异常处理。
2. 简述自定义异常的步骤。
答:
(1) 创建一个类继承自 Exception
(受检异常)或 RuntimeException
(非受检异常)。
(2) 提供至少一个构造方法,通常包含一个接收异常信息字符串的构造方法。
(3) 若有需要,可添加其他自定义方法。
3. 给出一个经典的自定义异常的简单示例代码
答:
以下是一个自定义异常示例,模拟在一个简单的银行账户系统中,当尝试取款金额超过账户余额时抛出异常。(日常我们判定余额不足并不会使用异常抛出,这里只是作示例。)
自定义异常类
// 自定义异常类(余额不足异常),继承自 Exception,属于受检异常
class InsufficientBalanceException extends Exception {
// 构造方法,接收异常信息
public InsufficientBalanceException(String message) {
super(message);
}
}
银行账户类
// 银行账户类
class BankAccount {
// 成员变量:账户余额
private double balance;
// 构造方法,初始化账户余额
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
// 取款方法,可能抛出 InsufficientBalanceException 异常
public void withdraw(double amount) throws InsufficientBalanceException {
if (amount > balance) {
// 当取款金额大于账户余额时,抛出异常
throw new InsufficientBalanceException("余额不足,无法取出 " + amount + " 元。");
}
balance -= amount;
System.out.println("成功取出 " + amount + " 元,当前余额为 " + balance + " 元。");
}
}
测试代码
// 测试类
public class CustomExceptionTest {
public static void main(String[] args) {
// 创建一个初始余额为 1000 元的银行账户
BankAccount account = new BankAccount(1000);
try {
// 尝试取款 1500 元
account.withdraw(1500);
} catch (InsufficientBalanceException e) {
// 捕获并处理异常,输出异常信息
System.out.println("捕获到异常: " + e.getMessage());
}
try {
// 尝试取款 500 元
account.withdraw(500);
} catch (InsufficientBalanceException e) {
System.out.println("捕获到异常: " + e.getMessage());
}
}
}
运行结果:
捕获到异常: 余额不足,无法取出 1500.0 元。
成功取出 500.0 元,当前余额为 500.0 元。
代码解释
- 自定义异常类
InsufficientBalanceException
:继承自Exception
,表示这是一个受检异常。它包含一个构造方法,用于接收异常信息并传递给父类。 - 银行账户类
BankAccount
:包含一个balance
字段表示账户余额,以及一个withdraw
方法用于取款。在withdraw
方法中,如果取款金额大于账户余额,就会抛出InsufficientBalanceException
异常。 - 测试类
CustomExceptionTest
:创建一个银行账户对象,分别尝试取款 1500 元和 500 元,并使用try-catch
块捕获和处理可能抛出的InsufficientBalanceException
异常。