当前位置: 首页 > news >正文

关于 java:4. 异常处理与调试

一、异常核心语法

1.1 try-catch-finally:异常捕获与处理结构

1)作用

  • 用于捕获和处理程序运行过程中可能发生的异常

  • 防止程序因异常中断,提高代码的鲁棒性(健壮性)

2)基本语法结构:

try {// 可能抛出异常的代码块
} catch (ExceptionType1 e1) {// 处理 ExceptionType1 类型的异常
} catch (ExceptionType2 e2) {// 处理 ExceptionType2 类型的异常
} finally {// 无论是否发生异常,都会执行(如关闭资源)
}

3)示例讲解:

public class Demo {public static void main(String[] args) {try {int a = 10 / 0; // 运行时异常:ArithmeticException} catch (ArithmeticException e) {System.out.println("错误:除数不能为零!");} finally {System.out.println("程序结束,释放资源。");}}
}

运行结果:

错误:除数不能为零!
程序结束,释放资源。

4)多个 catch

try {String s = null;System.out.println(s.length());
} catch (NullPointerException e) {System.out.println("空指针异常!");
} catch (Exception e) {System.out.println("其他异常:" + e.getMessage());
}

建议先写具体异常,再写父类(Exception),否则子类异常无法被捕获。

5)finally 详解

  • 无论 try 块是否抛出异常,finally 总会执行

  • 通常用于释放资源,如关闭文件、数据库连接

FileInputStream fis = null;
try {fis = new FileInputStream("data.txt");// 读取文件
} catch (IOException e) {System.out.println("读取失败");
} finally {if (fis != null) {try {fis.close(); // 一定要关闭资源} catch (IOException e) {e.printStackTrace();}}
}

1.2 throw:手动抛出异常对象

1)作用

throw 用于在代码中主动抛出一个异常实例,可以抛出任何 Throwable 的子类。

2)语法格式:

throw new 异常类型("异常描述");

3)示例:

public void checkAge(int age) {if (age < 0) {throw new IllegalArgumentException("年龄不能为负数");}
}
  • 程序执行到 throw 语句时会立即抛出异常并中断执行

  • 如果这个异常没有在方法内被 try-catch 捕获,必须用 throws 声明

1.3 throws:方法声明异常

1)作用

  • 用于方法签名中,声明该方法可能抛出哪些异常

  • 告诉调用者:你要么用 try-catch 处理,要么继续 throws

2)语法格式:

返回类型 方法名(...) throws 异常类型1, 异常类型2 {// 可能抛出异常的代码
}

3)示例:

public void readFile(String path) throws IOException {FileReader fr = new FileReader(path); // FileReader 会抛 IOException
}

调用时:

try {readFile("test.txt");
} catch (IOException e) {System.out.println("文件读取失败:" + e.getMessage());
}

4)throw vs throws 区别对比表:

特性throwthrows
用途抛出异常对象声明异常可能被抛出
位置方法体内方法声明处
后面跟的内容异常对象(new)异常类(不带 new)
示例throw new IOException("失败")throws IOException

1.4 综合例子:使用 throw + throws + try-catch

public class User {public void login(String username) throws Exception {if (username == null || username.isEmpty()) {throw new Exception("用户名不能为空");}System.out.println("登录成功");}public static void main(String[] args) {User user = new User();try {user.login(""); // 会抛出异常} catch (Exception e) {System.out.println("捕获到异常: " + e.getMessage());} finally {System.out.println("登录尝试结束");}}
}

输出:

捕获到异常: 用户名不能为空
登录尝试结束

1.5 实际用法

场景应用
开发中try-catch 处理用户输入、文件读取等不确定行为
SDK 调试通过日志堆栈 catch (Exception e) 观察调用流程
逆向分析中Hook 异常处理函数,绕过 throw 抛出的错误(例如:校验失败)
安全测试中利用错误提示、异常堆栈进行路径发现或代码注入入口分析

1.6 小结

try-catch-finally
│
├─ try:放入可能出错的代码
├─ catch:处理指定异常类型
├─ finally:一定执行,用于释放资源
│
throw:主动抛出异常对象
throws:方法声明可能抛出哪些异常

二、自定义异常类

2.1 自定义异常类的作用

在 Java 中,除了使用系统提供的异常(如 NullPointerException, IOException),我们还可以根据自己的业务逻辑需求定义新的异常类

自定义异常的典型用途:

  • 表示业务逻辑错误(例如:余额不足、权限异常)

  • 抛出更清晰可读、可追踪的错误

  • 与项目的模块/组件解耦,提高代码可维护性

  • 在调试或逆向中,定位异常的抛出源

2.2 自定义异常类的本质

Java 中所有异常类,最终都继承自:

java.lang.Throwable├── Error         // 错误(虚拟机错误等)└── Exception     // 异常├── RuntimeException(运行时异常)└── 其他受检异常(IOException 等)

我们自定义的异常通常继承自:

1)Exception(受检异常)

  • 必须用 try-catchthrows 处理

2)RuntimeException(非受检异常)

  • 编译器不强制捕获

  • 更灵活,适合应用内部逻辑异常

2.3 自定义异常类的语法

1)继承 Exception(受检异常)

public class MyCheckedException extends Exception {public MyCheckedException() {super();}public MyCheckedException(String message) {super(message);}public MyCheckedException(String message, Throwable cause) {super(message, cause);}
}

2)继承 RuntimeException(非受检异常)

public class MyRuntimeException extends RuntimeException {public MyRuntimeException(String message) {super(message);}
}

2.4 使用自定义异常的例子

示例 1:余额不足异常

// 自定义异常类
public class InsufficientBalanceException extends Exception {public InsufficientBalanceException(String message) {super(message);}
}

示例 2:在业务代码中使用

public class BankAccount {private double balance = 100.0;public void withdraw(double amount) throws InsufficientBalanceException {if (amount > balance) {throw new InsufficientBalanceException("余额不足,取款失败!");}balance -= amount;}
}

示例 3:调用者处理异常

public class Test {public static void main(String[] args) {BankAccount account = new BankAccount();try {account.withdraw(150.0); // 触发异常} catch (InsufficientBalanceException e) {System.out.println("异常捕获:" + e.getMessage());}}
}

2.5 规范建议(编写自定义异常)

建议项内容
类名Exception 结尾(如 LoginFailedException
构造方法提供 String messageThrowable cause 构造器
继承方式业务类建议继承 Exception,内部错误建议继承 RuntimeException
包名放在 com.xxx.exception 包下,统一管理

2.6 自定义异常在调试/逆向中的价值

在调试中:

  • 通过日志或堆栈跟踪定位自定义异常的抛出点

  • 比系统异常更具语义性,便于快速理解错误

在逆向中:

  • 某些 SDK 或加密逻辑会用自定义异常抛出校验错误

  • 通过 Frida/日志/trace 定位异常类名和抛出位置

  • 分析异常触发条件,进而绕过或构造伪装数据

2.7 小结

自定义异常类
├─ 为什么要自定义?
├─ 继承 Exception / RuntimeException
├─ 如何定义:构造器 + 命名规范
├─ 如何使用:抛出 throw + 声明 throws
├─ 实际场景:业务逻辑错误、逆向定位

三、常见异常类型

3.1 NullPointerException(空指针异常)

1)定义

空指针异常是指:访问了一个为 null 的引用对象的方法或字段时引发的异常。

java.lang.NullPointerException

2)常见触发场景

触发语句说明
obj.toString()obj 为 null,调用方法抛异常
obj.field访问成员变量时,obj 为 null
arr[0]arr 是 null,访问数组元素抛异常
list.get(0)list 是 null,而非空但索引越界时会抛 IndexOutOfBoundsException

3)示例代码

public class Demo {public static void main(String[] args) {String s = null;System.out.println(s.length()); // NullPointerException}
}

4)调试方式

  • 查看异常栈信息(Exception stack trace)

  • 从堆栈中定位异常行号和方法名

  • 使用 IDE 的断点或日志逐步排查 null 来源

栈追踪示例:

Exception in thread "main" java.lang.NullPointerExceptionat Demo.main(Demo.java:4)

5)如何防止空指针

方法示例
非空判断if (obj != null)
OptionalOptional.ofNullable(obj).orElse(defaultValue)
IDE 工具提醒IntelliJ IDEA 有 null 检测功能
Lombok 的 @NonNull 注解编译时校验是否为 null

3.2 ClassCastException(类强制类型转换异常)

1)定义

该异常表示:试图将某个对象强制转换为不是其实际类型的类时引发的异常

java.lang.ClassCastException

2)常见触发场景

Object obj = new Integer(5);
String str = (String) obj; // 报 ClassCastException

虽然 objObject 类型,但实际它是 Integer,不能强制转成 String

3)示例代码

public class Demo {public static void main(String[] args) {Object obj = "Hello";Integer num = (Integer) obj; // ClassCastException}
}

输出异常信息:

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integerat Demo.main(Demo.java:4)

4)如何避免 ClassCastException

方法示例
使用 instanceof 判断if (obj instanceof Integer)
使用泛型(推荐)List<String>List<Object> 更安全
明确接口和实现类边界不随意强转接口实现
开发规范约束明确参数传递和接收类型

3.3 调试角度分析

异常类型常见错误栈信息调试关键点
NullPointerException指向具体行的空对象访问追踪为 null 的变量,检查赋值
ClassCastException指出无法转换的两个类看实际对象的类型(getClass())和要转的类

3.4 在逆向/安全分析中的价值

1)辅助分析代码结构

  • App crash 日志中出现 ClassCastException 可能表示有 代码逻辑混淆或伪装

  • 出现 NullPointerException 时,反编译代码可定位关键对象未初始化

2)模拟异常绕过检查

try {if (!licenseValid) {throw new NullPointerException("验证失败");}
} catch (Exception e) {// 验证失败后中断return;
}

可以 Hook 掉这个 throw,或者强改 licenseValid 为 true,绕过验证。

3.5 小结

异常本质触发时机预防方法
NullPointerException空引用访问访问 null 的变量、方法或数组非空检查、Optional
ClassCastException类型错误强转成非实际类型instanceof 判断、泛型

四、堆栈追踪分析

4.1 什么是堆栈追踪(Stack Trace)?

堆栈追踪是 Java 程序在运行中发生异常或错误时,JVM 自动打印的一系列方法调用栈信息,描述了从异常发生点逐层向上传递调用关系

示例异常信息(Stack Trace):

Exception in thread "main" java.lang.NullPointerExceptionat com.example.MyClass.myMethod(MyClass.java:10)at com.example.App.main(App.java:5)

4.2 Stack Trace 的结构组成

以典型的一条栈帧为例:

at com.example.MyClass.myMethod(MyClass.java:10)
部分含义
at表示当前调用栈的一帧
com.example.MyClass异常发生的类
myMethod异常发生的方法
(MyClass.java:10)源码文件及第几行发生了异常(10 行)

4.3 堆栈追踪常见异常样式(举例对照)

1)NullPointerException(空指针)

Exception in thread "main" java.lang.NullPointerExceptionat com.demo.DemoClass.printName(DemoClass.java:15)at com.demo.DemoClass.main(DemoClass.java:7)

分析:

  • 异常发生在 DemoClass.java 的第 15 行

  • 是从 main() 调用 printName() 时触发的

2)ClassCastException(类强制转换)

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integerat com.test.CastTest.main(CastTest.java:8)

分析:

  • 明确告诉你:实际是 String,你试图转成 Integer

  • 报错发生在第 8 行

4.4 如何进行堆栈追踪分析?

步骤 1:从上到下阅读栈帧(第一条才是出错点)

  • 第一条是异常抛出的具体位置

  • 后面的每条是“谁调用了它”

步骤 2:对照源码或反编译代码,定位具体代码行

  • 使用 IDE 跳转对应行(Ctrl + 单击)

  • 或用 jadx, JD-GUI, Fernflower 反编译查看 .class

步骤 3:结合异常类型,分析出错条件

  • 是不是参数为 null?

  • 是不是数据转换错误?

  • 是不是调用了非法对象?

4.5 逆向调试场景中的应用

1)分析 App 崩溃日志

Caused by: java.lang.RuntimeException: 解密失败at com.app.secure.SecurityManager.decrypt(SecurityManager.java:87)at com.app.net.NetHandler.getUserInfo(NetHandler.java:122)

这说明:

  • decrypt() 方法出错,可能在使用 AES、RSA 时 key 异常

  • 你可以重点 hook 这一段代码,或者跟进加密过程逻辑

2)分析 Web 安全漏洞、异常行为

java.lang.IllegalArgumentException: 参数不合法at com.web.api.AuthHandler.checkToken(AuthHandler.java:52)at com.web.api.UserController.getUser(UserController.java:19)

说明:

  • checkToken 方法校验失败,抛出异常

  • 可用于判断接口是否有 token 依赖点,或可伪造点

3)结合 Frida 实现实时监控异常

可以使用 Frida 追踪所有异常抛出点:

Java.perform(function () {var Exception = Java.use("java.lang.Exception");Exception.$init.overload('java.lang.String').implementation = function (msg) {console.log("Exception 被抛出: " + msg);return this.$init(msg);};
});

4.6 分析:Caused bySuppressed

Caused by

有时异常是嵌套异常,会看到:

Exception in thread "main" java.lang.Exception: 顶层异常
Caused by: java.io.IOException: 文件不存在at com.file.Reader.read(Reader.java:45)
  • Caused by 表示底层真正触发的异常

  • 要分析最底层原因

Suppressed

当使用 try-with-resources 时,可能出现:

Suppressed: java.lang.Exception: 关闭资源失败

说明主异常之后还有资源释放过程中的异常

4.7 小结(堆栈分析三步法)

堆栈分析三步法:
1. 定位第 1 行出错代码(准确行号)
2. 判断异常类型及触发原因
3. 向上追踪调用链,分析调用过程

4.8 实战建议

场景技巧
逆向异常分析拿到 crash log,反编译对应类,查找触发条件
安全测试中断点排查抓住抛异常的函数,设置 Frida Hook 或 JDWP 调试点
Web 渗透中判断逻辑利用返回错误堆栈,看系统是怎么解析参数和验证身份
http://www.xdnf.cn/news/762373.html

相关文章:

  • C#数字图像处理(二)
  • IO流1——体系介绍和字节输出流
  • 如何用利用deepseek的API能力来搭建属于自己的智能体-优雅草卓伊凡
  • 【AI面试秘籍】| 第25期:RAG的关键痛点及解决方案深度解析
  • OpenGL、GLUT、freeGLUT 与 GLFW 的区别
  • 【渲染】拆解《三国:谋定天下》场景渲染技术
  • C++实现汉诺塔游戏自动完成
  • [AD] CrownJewel-1 Logon 4799+vss-ShadowCopy+NTDS.dit/SYSTEM+$MFT
  • QT中子线程触发主线程弹窗并阻塞等待用户响应
  • Ⅰ.计算机二级选择题(C语言概述)
  • 第二章 机器学习基本概念
  • 【RocketMQ 生产者和消费者】- 生产者发送同步、异步、单向消息源码分析(1)
  • 利用IEEE 802.15.4z-IR UWB系统进行手势检测
  • Python中scapy库详细使用(强大的交互式数据包操作程序和库)
  • 基于 Three.js 的文本粒子解体效果技术原理剖析
  • 002 dart刷题
  • 车载控制器的“机电一体化”深度集成
  • 自编码器Auto-encoder(李宏毅)
  • Go语言实现高性能分布式爬虫系统 - 设计与实践
  • 在线音乐服务器测试报告
  • Codeforces 1027 Div3(ABCDEF)
  • 过滤攻击-隐私保护
  • 淘宝商品详情页有哪些常见的动态加载技术?
  • Python训练营---Day42
  • pikachu通关教程- over permission
  • 深入理解 C++11 中的 std::move —— 移动语义详解(小白友好版)
  • 数字创新智慧园区建设及运维方案
  • lidar和imu的标定(三)平面约束的方法
  • 51单片机基础部分——LED
  • 船舶二阶非线性响应方程的EKF与UKF参数辨识