Java 异常处理详解
一、异常处理的重要性
-
错误分类 :程序运行中可能出现用户输入错误(如
NumberFormatException
)、资源缺失(如FileNotFoundException
)、系统级错误(如内存耗尽、网络中断)等。 -
处理方式对比:
- 错误码(如 C 语言) :通过返回整数表示错误,需手动枚举和判断,代码繁琐。
- Java 异常机制 :通过
class
封装错误类型,可在任意位置抛出,上层统一捕获,分离错误处理逻辑。
二、Java 异常继承体系
plaintext
Object
├─ Throwable(所有异常/错误的根)
│ ├─ Error(严重错误,程序无法处理,无需捕获)
│ │ 例:`OutOfMemoryError`(内存耗尽)、`StackOverflowError`(栈溢出)、`NoClassDefFoundError`(类缺失)
│ └─ Exception(可处理的运行时错误)
│ ├─ RuntimeException(非受检异常,编译器不强制捕获)
│ │ 例:`NullPointerException`(空指针)、`IndexOutOfBoundsException`(索引越界)、`IllegalArgumentException`(非法参数)
│ └─ 非RuntimeException(受检异常,编译器强制要求捕获或声明抛出)
│ 例:`IOException`(IO错误)、`UnsupportedEncodingException`(编码不支持)、`SQLException`(数据库错误)
三、受检异常(Checked Exception)与非受检异常(Unchecked Exception)
特性 | 受检异常(Exception 非 Runtime 子类) | 非受检异常(RuntimeException 及其子类 / Error) |
---|---|---|
编译器检查 | 强制要求try...catch 捕获或throws 声明 |
不强制,可自由选择是否处理 |
处理责任 | 调用方必须处理 | 通常由程序员修复代码逻辑错误,而非运行时处理 |
常见场景 | 外部资源操作(文件、网络、数据库) | 程序逻辑错误(空指针、越界、非法参数等) |
四、异常处理核心语法:try...catch
与throws
-
try...catch
捕获异常:-
将可能抛出异常的代码放入
try
块,通过多个catch
块捕获不同类型的异常(子类异常需放在父类之前)。 -
示例:
javatry { String data = readFile("data.txt"); // 可能抛出FileNotFoundException } catch (FileNotFoundException e) { System.out.println("文件未找到:" + e.getMessage()); e.printStackTrace(); // 打印异常栈轨迹 } catch (IOException e) { // 处理更宽泛的异常 System.out.println("IO错误:" + e); }
-
-
throws
声明异常:-
方法定义时用
throws
声明可能抛出的受检异常,将处理责任交给调用方。 -
示例:
arduinostatic byte[] toGBK(String s) throws UnsupportedEncodingException { return s.getBytes("GBK"); // 声明抛出受检异常,调用方必须处理 }
-
-
注意事项:
- 受检异常必须处理:未捕获或声明受检异常会导致编译错误。
- 避免空捕获 :捕获异常后至少记录日志(如
printStackTrace()
),避免静默忽略错误。 main
方法作为最后屏障 :可声明throws Exception
直接终止程序,或在main
中统一捕获所有异常。
捕获异常
一、核心概念
在 Java 中,try...catch
机制用于捕获可能抛出的异常。将可能引发异常的代码置于try
块中,通过catch
块捕获对应的Exception
及其子类。
二、多 catch 语句
-
执行逻辑 :JVM 按顺序匹配
catch
块,匹配成功后执行对应代码块,不再继续匹配。 -
顺序要求:子类异常必须写在父类异常前面,否则子类异常永远无法被捕获。
- 错误示例:
catch(IOException e)
后接catch(UnsupportedEncodingException e)
(子类在后,无法捕获)。 - 正确示例:先子类
UnsupportedEncodingException
,再父类IOException
。
- 错误示例:
三、finally 语句
-
作用 :无论是否发生异常,
finally
块中的代码都会执行,用于资源清理等必须执行的逻辑。 -
特点
- 非必需,可省略。
- 始终最后执行:正常执行完
try
后执行;发生异常时,执行完匹配的catch
后执行。 - 可单独使用
try...finally
(无需catch
,但需方法声明抛出异常)。
四、捕获多种异常
-
场景 :处理逻辑相同但无继承关系的异常(如
IOException
和NumberFormatException
)。 -
语法 :使用
|
合并多个异常类型,简化代码。javacatch(IOException | NumberFormatException e) { // 统一处理逻辑 }
抛出异常
一、异常的传播
- 传播机制 :当方法抛出异常且未被捕获时,异常会向上层调用链传播,直至被
try...catch
捕获。 - 调试工具 :通过
printStackTrace()
打印异常调用栈,显示异常发生的方法调用层次及源码行号,便于定位问题。示例中NumberFormatException
的栈信息清晰展示了从main()
到Integer.parseInt()
的调用路径。
二、抛出异常的核心要点
-
基本步骤
- 创建
Exception
实例(如new NullPointerException()
)。 - 使用
throw
语句抛出,可合并为一行代码(throw new NullPointerException();
)。
- 创建
-
异常转换与原始信息保留
- 捕获异常后若需抛出新异常,应在新异常构造器中传入原始异常(如
throw new IllegalArgumentException(e)
),通过Caused by
保留完整栈信息,避免丢失 "第一案发现场"。 - 最佳实践:始终保留原始异常,确保调用链信息完整。
- 捕获异常后若需抛出新异常,应在新异常构造器中传入原始异常(如
-
finally
语句的执行规则- 即使在
try
或catch
中抛出异常,finally
仍会执行,JVM 会先执行finally
再抛出异常。
- 即使在
三、异常屏蔽与处理
- 屏蔽现象 :若
finally
中抛出新异常,catch
中准备抛出的异常会被屏蔽(称为Suppressed Exception
)。 - 保留被屏蔽异常 :通过
Throwable.addSuppressed(origin)
将原始异常添加到新异常中,可通过getSuppressed()
获取。 - 最佳实践 :绝大多数情况下,避免在
finally
中抛出异常,减少调试复杂度。
四、异常信息的重要性
- 提问或调试时,必须提供完整的异常栈信息(包括
Caused by
和Suppressed
部分),否则难以定位问题根源。
五、核心总结
关键点 | 说明 |
---|---|
异常传播 | 异常沿调用链向上抛出,printStackTrace() 打印栈信息辅助定位。 |
抛出异常 | throw 语句创建并抛出异常,转换异常时传入原始异常以保留完整栈(Caused by )。 |
finally 规则 |
无论是否抛出异常,finally 都会执行;避免在finally 中抛异常,防止屏蔽原始异常。 |
异常屏蔽 | 通过addSuppressed() 保存被屏蔽异常,通常无需处理,但需知晓机制。 |
调试建议 | 贴出完整异常栈(含Caused by ),是定位问题的关键线索。 |
自定义异常
一、Java 标准库异常体系
-
核心结构
- 顶层异常为
Exception
,分为RuntimeException
(运行时异常)和受检异常(如IOException
)。 - 常见运行时异常:
NullPointerException
、IllegalArgumentException
、IndexOutOfBoundsException
等。 - 常见受检异常:
IOException
、SQLException
、ParseException
等。
- 顶层异常为
二、自定义异常的设计原则
-
优先复用标准异常
- 当参数检查不合法时,应抛出
IllegalArgumentException
及其子类(如NumberFormatException
)。 - 避免重复造轮子,标准异常已覆盖大多数通用场景。
- 当参数检查不合法时,应抛出
-
定义根异常(BaseException)
- 继承选择 :推荐从
RuntimeException
派生,作为业务异常的根类,避免强制try-catch
。 - 作用:统一业务异常体系,便于集中处理和区分系统级异常(如 JDK 原生异常)。
- 继承选择 :推荐从
-
派生业务异常
- 从
BaseException
派生出具体业务异常类,如UserNotFoundException
(用户未找到)、LoginFailedException
(登录失败)。 - 示例代码:
javapublic class BaseException extends RuntimeException { // 构造方法需覆盖父类所有重载形式 public BaseException() { super(); } public BaseException(String message) { super(message); } public BaseException(String message, Throwable cause) { super(message, cause); } public BaseException(Throwable cause) { super(cause); } } public class UserNotFoundException extends BaseException { /* 同上构造方法 */ } public class LoginFailedException extends BaseException { /* 同上构造方法 */ }
- 从
三、构造方法的实现要求
- 完整覆盖父类构造:需包含无参、单字符串参数、字符串 + Throwable、单 Throwable 参数四种构造方法,确保异常创建的灵活性。
- IDE 辅助:通过 IDE 自动生成构造方法,避免手动编写错误。
四、最佳实践
-
异常分类清晰
- 根异常
BaseException
作为业务异常顶层,便于全局捕获(如catch (BaseException e)
统一处理业务逻辑异常)。
- 根异常
-
减少受检异常
- 继承
RuntimeException
的非受检异常,无需在方法签名中声明throws
,简化代码结构。
- 继承
-
提供有意义的错误信息
- 构造异常时传入具体消息(如
new UserNotFoundException("用户ID 123未找到")
),便于调试定位。
- 构造异常时传入具体消息(如
Java断言
一、核心概念
- 断言定义 :一种调试工具,用于在开发和测试阶段验证程序状态,通过
assert
关键字实现。 - 作用:确保代码逻辑符合预期,捕获潜在错误,辅助调试。
二、语法与用法
-
基本语法
java
javaassert 条件表达式; // 断言条件为 true,否则抛出 AssertionError assert 条件表达式 : 断言消息; // 失败时附带自定义消息
- 例:
assert x >= 0 : "x 必须非负";
- 例:
-
执行逻辑
- 条件为
false
时,抛出AssertionError
(Error 的子类),终止程序。 - 断言消息可选,用于增强错误信息可读性。
- 条件为
三、核心特点
-
适用场景
- 仅限开发 / 测试阶段:用于检查 "不应该发生" 的错误(如参数合法性的极端假设)。
- 不处理可恢复错误 :对可预期的程序错误(如
null
参数),应显式抛出异常(如IllegalArgumentException
)并由上层处理,而非断言。
-
默认行为
- JVM 默认关闭断言 ,
assert
语句会被忽略,需手动启用。
- JVM 默认关闭断言 ,
四、启用断言的方式
-
全局启用
bashjava -enableassertions 或 -ea 类名
- 例:
java -ea Main
- 例:
-
局部启用
- 指定类 :
-ea:包名.类名
(如-ea:com.example.Main
)。 - 指定包(含子包) :
-ea:包名...
(如-ea:com.example...
)。
- 指定类 :
反射
反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息。所以,反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。
Class 类
一、Class 类本质
-
定义 :JVM 为每个加载的
class
(含interface
)创建的唯一实例,存储类的完整元信息(类名、包名、父类、接口、字段、方法等)。 -
特性
- 由 JVM 动态加载,首次使用时加载到内存。
Class
类构造方法为private
,仅 JVM 可创建实例,用户无法直接实例化。- 每个
class
/interface
对应唯一Class
实例,可通过==
比较是否为同一类型。
二、获取 Class 实例的三种方式
方法 | 示例 | 说明 |
---|---|---|
类字面量(.class ) |
Class<String> cls1 = String.class; |
直接通过类的静态变量class 获取,编译期确定类型 |
实例的getClass() |
String str = "hello"; <br> Class<?> cls2 = str.getClass(); |
通过对象实例调用,返回其运行时类型的Class 实例 |
Class.forName("全类名") |
Class<?> cls3 = Class.forName("java.lang.String"); |
运行期根据全类名动态加载,可能抛出ClassNotFoundException |
- 关键点 :三种方式获取的
Class
实例相同,可通过==
验证(如cls1 == cls2 == cls3
为true
)。
三、Class 实例核心功能
1. 类型信息查询
方法 | 作用 | 示例 |
---|---|---|
getName() |
返回完整类名(含包名) | String[].class.getName() → [Ljava.lang.String; (数组类名特殊格式) |
getSimpleName() |
返回简单类名(无包名) | String.class.getSimpleName() → String |
getPackage() |
获取包信息 | String.class.getPackage().getName() → java.lang |
isInterface() |
判断是否为接口 | Runnable.class.isInterface() → true |
isEnum() |
判断是否为枚举类 | Month.class.isEnum() → true |
isArray() |
判断是否为数组类型 | String[].class.isArray() → true |
isPrimitive() |
判断是否为基本类型 | int.class.isPrimitive() → true |
2. 创建类实例
-
方法 :
newInstance()
-
示例
javaClass<String> cls = String.class; String obj = cls.newInstance(); // 等价于 `new String()`(调用无参构造方法)
-
限制 :仅能调用
public
无参构造方法,不支持有参或非public
构造方法。
3. 与instanceof
的区别
特性 | instanceof |
Class 实例比较(== ) |
---|---|---|
核心功能 | 判断对象是否为某类型或其子类型(支持多态) | 精确判断是否为同一类型(不考虑继承关系) |
示例 | Integer num instanceof Number → true (多态匹配父类) |
num.getClass() == Number.class → false (Integer 是Number 子类,类型不同) |
四、动态加载机制
-
JVM 特性:按需加载,首次使用时加载类到内存(如首次调用类的方法、创建实例或访问静态成员时)。
-
应用场景:运行期根据条件加载不同实现类(如日志框架优先级选择)
java// 示例:优先使用Log4j,不存在则用JDK日志 boolean isLog4jPresent = false; try { Class.forName("org.apache.log4j.Logger"); isLog4jPresent = true; // 加载Log4j类,存在则使用 } catch (ClassNotFoundException e) { // 未找到Log4j,使用JDK日志 }
五、特殊类型的 Class 实例
-
基本类型 :JVM 预定义
Class
实例,如int.class
、boolean.class
、void.class
。 -
数组类型
- 引用类型数组:类名为
[L全类名;
(如String[].class
→[Ljava.lang.String;
)。 - 基本类型数组:类名为
[基本类型缩写
(如int[]
→[I
,boolean[]
→[Z
)。
- 引用类型数组:类名为
Java 反射之访问字段
一、核心知识点:通过反射获取字段信息
-
获取
Field
实例的方法getField(name)
:获取某个public 字段(包括父类)。getDeclaredField(name)
:获取当前类的任意字段(不包括父类,可获取 private/protected/default 字段)。getFields()
:获取所有public 字段(包括父类)。getDeclaredFields()
:获取当前类的所有字段(不包括父类)。
-
Field
对象包含的字段信息getName()
:返回字段名称(如"name"
)。getType()
:返回字段类型(Class
实例,如String.class
,数组类型用[B
表示byte[]
)。getModifiers()
:返回字段修饰符(int
值),可通过Modifier
类判断(如Modifier.isPrivate(m)
)。
二、核心操作:读写字段值
-
获取字段值(
Field.get(Object instance)
)- 示例:
Object value = field.get(p)
,获取实例p
的字段值。 - 访问限制 :若字段为非 public(如 private),需先调用
field.setAccessible(true)
突破访问限制(可能受SecurityManager
限制,如无法修改 Java 核心类字段)。
- 示例:
-
设置字段值(
Field.set(Object instance, Object value)
)- 示例:
field.set(p, "新值")
,修改实例p
的字段值。 - 注意 :同样需通过
setAccessible(true)
访问非 public 字段,支持修改基本类型和引用类型字段。
- 示例:
三、代码示例与注意事项
-
示例代码逻辑
- 获取字段 :通过
Class
实例(如Student.class
或p.getClass()
)调用字段获取方法。 - 突破封装:反射可访问私有字段,但会破坏类的封装性,仅用于工具 / 框架等非常规场景。
- 获取字段 :通过
-
安全限制
setAccessible(true)
可能被SecurityManager
阻止(如 Java 核心包java.*
/javax.*
的类),确保 JVM 安全。
四、小结
-
反射访问字段的核心步骤
- 通过
Class
获取Field
实例(区分是否包含父类、是否 public)。 - 通过
Field
获取字段元信息(名称、类型、修饰符)。 - 通过
get()
/set()
读写字段值,非 public 字段需setAccessible(true)
。
- 通过
-
适用场景与风险
- 用途:底层框架、工具开发(未知对象结构时动态操作字段)。
- 风险:破坏封装性,代码繁琐,受安全策略限制,需谨慎使用。
调用方法
二、核心内容总结
1. 获取 Method 对象的方法
通过Class
实例获取Method
对象,支持不同范围和权限的方法获取:
getMethod(String name, Class... parameterTypes)
:获取类及其父类的public 方法(需指定参数类型)。getDeclaredMethod(String name, Class... parameterTypes)
:获取当前类的任意方法(包括 private,不包括父类)。getMethods()
:获取类及其父类的所有 public 方法 (返回Method[]
)。getDeclaredMethods()
:获取当前类的所有方法 (包括 private,返回Method[]
)。
2. Method 对象的信息获取
通过Method
实例可获取方法的元数据:
getName()
:返回方法名称(如"getScore"
)。getReturnType()
:返回返回值类型(Class
实例,如String.class
)。getParameterTypes()
:返回参数类型数组(Class[]
,如{String.class, int.class}
)。getModifiers()
:返回修饰符(int
类型,通过位运算解析 public/private 等)。
3. 调用方法的方式
-
实例方法调用 :使用
Object invoke(Object instance, Object... args)
,instance
为方法所属对象,args
为参数。
示例:javaMethod m = String.class.getMethod("substring", int.class); String result = (String) m.invoke("Hello", 2); // 调用"ello"
-
静态方法调用 :
invoke
的第一个参数为null
(无需实例)。
示例:javaMethod m = Integer.class.getMethod("parseInt", String.class); Integer num = (Integer) m.invoke(null, "123"); // 调用静态方法
-
非 public 方法调用 :需先通过
setAccessible(true)
破除访问限制(可能受SecurityManager
限制)。
示例:javaMethod privateMethod = MyClass.class.getDeclaredMethod("privateMethod"); privateMethod.setAccessible(true); privateMethod.invoke(instance);
4. 多态特性
反射调用方法时遵循多态原则:调用实际对象类型的覆写方法(即使通过父类Class
获取方法)。
示例 :
子类Student
覆写父类Person
的hello()
方法,通过Person.class.getMethod("hello")
获取方法后,调用Student
实例仍执行子类实现。
三、关键代码示例
-
获取不同权限的方法:
javaClass<Student> clazz = Student.class; clazz.getMethod("publicMethod"); // 公共方法(含父类) clazz.getDeclaredMethod("privateMethod"); // 当前类私有方法
-
处理方法重载 :通过参数类型区分同名方法,如获取
substring(int start)
和substring(int start, int end)
需指定不同参数类型。 -
异常处理 :调用
invoke
可能抛出IllegalAccessException
(权限问题)、InvocationTargetException
(方法内部异常)等,需显式处理或声明抛出。
四、小结
- 反射调用方法的核心步骤 :获取
Class
→获取Method
→调用invoke
(处理权限和参数)。 - 方法访问范围 :
getMethod
用于公共方法(含继承),getDeclaredMethod
用于当前类任意方法(需破除权限)。 - 多态支持:反射调用严格遵循 Java 多态规则,实际调用对象的覆写方法。
- 应用场景:动态调用未知方法、框架底层扩展(如 Spring 依赖注入)、单元测试工具等。
调用构造方法(Java 反射机制)
一、实例创建的常规方式与反射基础
-
常规实例创建
使用
new
操作符直接创建实例:javaPerson p = new Person();
-
反射创建实例(无参构造)
通过
Class.newInstance()
方法调用 public 无参数构造方法:javaPerson p = Person.class.newInstance();
局限:仅支持无参且 public 的构造方法,无法处理有参或非 public 构造方法。
二、Constructor
对象:反射调用任意构造方法
Constructor
类封装了构造方法的详细信息,可用于创建实例,支持 有参、非 public 构造方法。
-
获取
Constructor
对象的方法getConstructor(Class<?>... parameterTypes)
:获取 public 构造方法(指定参数类型)。getDeclaredConstructor(Class<?>... parameterTypes)
:获取 任意访问权限 的构造方法(包括非 public)。getConstructors()
:获取所有 public 构造方法(返回数组)。getDeclaredConstructors()
:获取所有构造方法(包括非 public,返回数组)。
注意:构造方法属于当前类,与父类无关,无多态特性。
-
调用构造方法创建实例
使用
newInstance(Object... args)
传入参数创建实例:java// 调用 Integer(int) 构造方法 Constructor<Integer> cons1 = Integer.class.getConstructor(int.class); Integer n1 = cons1.newInstance(123); // 结果:123 // 调用 Integer(String) 构造方法 Constructor<Integer> cons2 = Integer.class.getConstructor(String.class); Integer n2 = cons2.newInstance("456"); // 结果:456
-
访问非 public 构造方法
需通过
setAccessible(true)
强制设置访问权限(可能受安全策略限制):javaConstructor<MyClass> cons = MyClass.class.getDeclaredConstructor(); cons.setAccessible(true); // 允许访问非 public 构造方法 MyClass obj = cons.newInstance();
三、核心要点总结
-
Constructor
的作用封装构造方法信息,支持调用 任意访问权限、任意参数的构造方法 ,弥补
Class.newInstance()
的局限。 -
关键方法对比
方法名 功能描述 访问权限 参数类型匹配 getConstructor
获取 public 构造方法 public 精确匹配参数 getDeclaredConstructor
获取任意构造方法(包括非 public) 任意 精确匹配参数 getConstructors
获取所有 public 构造方法(数组) public 全部 getDeclaredConstructors
获取所有构造方法(包括非 public) 任意 全部 -
注意事项
- 非 public 构造方法需通过
setAccessible(true)
解锁访问,可能抛出安全异常。 - 构造方法调用结果始终返回实例,无返回值(与普通方法不同)。
- 非 public 构造方法需通过