Java Crash基础:异常类型与堆栈归纳总结

说实话,搞了这么多年 Java,我见过最多的崩溃就是 NullPointerException。但要想真正读懂 Crash 日志,你得先理清 Java 异常体系。这一章,我把 Java 异常类型和堆栈信息给你梳理清楚。

Java 异常体系概览

Java 异常其实就两大类:Checked Exception(受检异常)和 Unchecked Exception(非受检异常)。后者又分 RuntimeException 和 Error。Error 是 JVM 层面的严重问题,比如 OutOfMemoryError、StackOverflowError,正常代码里基本处理不了。我们平时说的 "Java Crash",绝大部分都是 RuntimeException 或其子类。

重点记住:你的 App 崩溃日志里,异常类型通常是 RuntimeException 的子类,比如 NullPointerException、IllegalArgumentException、IndexOutOfBoundsException 等。

常见异常类型速查表

异常类型触发场景我踩过的坑
NullPointerException调用 null 对象的方法或属性一次用 findViewById 后没判空,直接崩溃
IndexOutOfBoundsException数组/列表索引越界写 for 循环时用了 <= length
ClassCastException强制类型转换失败JSON 解析时类型不对,直接炸了
IllegalArgumentException方法参数非法传了负数给数组长度
ArithmeticException算术异常(如除 0)做除法前没检查除数
OutOfMemoryError堆内存不足加载大图没压缩,OOM 了

堆栈信息的结构

每个 Java Crash 都会打印出一个堆栈轨迹(StackTrace)。说白了,就是告诉你:哪行代码出了问题,以及是怎么调用到那儿的。格式大概是这样的:

Exception in thread "main" java.lang.NullPointerException
    at com.example.Main.processData(Main.java:25)
    at com.example.Main.main(Main.java:12)

第一行:异常类型 + 线程名。后面跟着一行行 at 开头的调用链。最顶上是异常发生的位置,往下是调用者。我一般从最顶部开始看,先定位到具体文件和行号。

如何阅读堆栈

  1. 看最上面的 at 行 – 那就是异常真实抛出点。
  2. 注意 Caused by – 有些异常有链式原因,比如在 catch 块里又抛了新异常,会显示 Caused by。
  3. 关注包名和类名 – 如果是你自己的代码,直接改;如果是第三方库或系统 framework,可能得看调用参数。

小技巧:我经常在日志里搜索 "at com.yourcompany.",只筛选自己项目的代码,效率高很多。

实战:从堆栈中提取关键信息

假设你收到这样一段日志:

java.lang.IndexOutOfBoundsException: Index 5, Size 5
    at java.util.ArrayList.arrayIndexOutOfBounds(ArrayList.java:)
    at java.util.ArrayList.get(ArrayList.java:431)
    at com.example.ui.UserAdapter.onBindViewHolder(UserAdapter.java:45)
    at android.support.v7.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:6781)
    ...

注意看:"Index 5, Size 5" – 索引 5 超过了有效范围(size=5),说明你在 get(5) 时列表只有 5 个元素(索引 0-4)。再看 at 行:你的 UserAdapter 的第 45 行调用了 get 方法。这通常是因为数据列表和界面展示不同步导致的。我遇到过类似情况,当时是异步更新完数据后忘了通知 Adapter。

捕获 Java 异常的最佳实践

你不能把所有异常都 try-catch 掉,那样会掩盖真正的问题。我个人的原则是:

  • 在入口处(比如 Activity 的 onCreate、onClick)加全局 try-catch,记录日志并友好提示用户。
  • 在关键业务逻辑中,只捕获你能处理的异常,其他的抛出去或交给全局处理器。
  • 使用 Thread.setDefaultUncaughtExceptionHandler 来捕获未处理的异常,做最后一道防线。
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
    // 保存 crash 日志到本地,下次启动时上传
    Log.e("CrashHandler", "Uncaught exception", throwable);
});

注意:在 Android 中,如果你在子线程抛了异常但没有 catch,进程会直接挂掉。所以务必给子线程也加 UncaughtExceptionHandler。

本章知识体系图

下面这张图展示了 Java Crash 异常类型的分类以及堆栈解析的流程,帮助你把零散知识点串起来。

Java Crash 基础:异常类型与堆栈解析 Java Throwable Error (不可恢复) OutOfMemoryError StackOverflowError Exception (可恢复/可捕获) Checked Exception RuntimeException NullPointerException IndexOutOfBounds ClassCastException IllegalArg... 堆栈解析:读取异常类型 → 定位 at 行 → 分析 Caused by → 修复代码 💡 日常 Crash 主要是 RuntimeException 堆栈最顶部是真实异常点

总结

弄懂异常类型和堆栈结构,是解析 Java Crash 的第一步。你拿到一个崩溃日志,先看异常类型,再看堆栈顶部的 at 行,最后结合自己的代码逻辑去修复。至于更高级的 Native Crash、ANR 等,后面章节再逐步展开。

嗯,这一章就到这里。记得多动手模拟几次 Crash,日志看多了自然就熟了。