商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > Java中的异常最新全解

Java中的异常最新全解

  发布于2026-07-27 阅读(0)

扫一扫,手机访问

在Ja va中,异常处理是程序设计中绕不开的核心机制之一。它专门用来应对程序运行过程中可能出现的各种意外情况。Ja va的异常处理体系主要围绕五个关键点展开:异常类型、异常的抛出、异常的捕获、自定义异常以及finally块的使用。下面就来系统梳理一下,看完之后你会对如何编写更健壮的代码有更清晰的认识。

Ja va中的异常最新全解

前言

写Ja va程序的时候,理想情况当然是个好东西,但现实往往不按剧本来。编译阶段一切顺利,可一运行起来,各种问题就冒出来了,比如:

  • 除数不能为0
  • 数组下标越界
  • 字符串为 null
  • 文件找不到
  • 输入的数据格式不正确
  • 网络连接失败
  • 类型转换失败

这些都属于程序运行过程中的异常情况。如果程序员对这些异常完全视而不见,那后果就是程序直接报错终止,甚至影响整个系统的稳定性——用户体验?那更是无从谈起。

所以说,Ja va里的异常知识绝不是“可有可无”的附加内容,而是写出健壮程序的必修课。一个真正能用的程序,不能只考虑“正常情况下怎么跑”,还必须想清楚:

  • 出现错误时怎么办?
  • 错误发生后要不要继续执行?
  • 如何给调用者一个明确的提示?
  • 怎么避免程序直接崩溃?
  • 怎样把错误信息妥善保留下来,方便后续排查?

Ja va之所以在企业开发中这么受欢迎,一个很重要的原因就是它对异常机制做了非常完整的设计。基于 Throwable 体系,Ja va把运行中的问题分门别类,并提供了 trycatchfinallythrowthrows 这一整套处理异常的语法工具。

可以这样理解:

异常机制的作用,不是让程序永远不出错,而是让程序在出错时仍然可控。

接下来,我们就围绕Ja va异常机制进行一次系统性的梳理。从概念、分类、语法、执行流程,到常见异常、处理方式、自定义异常、开发规范,尽量把各个角度都讲明白。学习这一章,不能只记住几个关键字,更重要的是理解异常的本质、传播方式以及处理思路。

通过本章学习,可以掌握:

  • 什么是异常
  • 异常与普通错误提示的区别
  • Ja va 异常体系结构
  • ThrowableErrorException 之间的关系
  • 编译时异常和运行时异常的区别
  • try-catch-finally 的基本用法
  • 多个 catch 的匹配顺序
  • finally 的作用
  • throwthrows 的区别
  • 如何自定义异常类
  • 常见异常类型及触发场景
  • 异常处理中常见错误与开发建议

一、什么是异常

1. 异常的基本概念

异常,英文叫 Exception。在Ja va中,异常指的就是:程序在运行过程中间出现的不正常情况。这里要特别注意两个关键词:运行过程中不正常情况。也就是说,异常通常不是语法错误,而是程序在执行时遇到了超出预期的问题。

举个例子:

int a = 10;
int b = 0;
System.out.println(a / b);

这段代码在编译时完全可以通过,因为语法没问题。但一运行就会报错,因为0不能做除数。这就是典型的异常。

2. 异常和语法错误的区别

很多初学者容易把“编译错误”和“异常”混为一谈。比如下面这个:

int a = "hello";

这不是异常,而是编译错误。因为代码在编译阶段就已经不合法,程序根本无法运行。而下面这个:

String str = null;
System.out.println(str.length());

编译没问题,但运行时会出现 NullPointerException。这才属于异常。

所以可以这样区分:

编译错误

  • 语法、类型、结构不合法
  • 程序连运行机会都没有

异常

  • 编译可以通过
  • 运行时出现问题

3. 为什么程序需要异常机制

如果没有异常机制,程序一旦出错,通常只能直接崩溃,没有明确提示,调用者也不知道哪里出了问题,程序员也难以定位原因。有了异常机制之后,我们可以:捕获错误、给出清晰信息、必要时中断执行、必要时继续执行后续逻辑、统一处理错误情况。所以,异常机制本质上是在帮助程序员管理运行时风险。

二、生活化理解异常

1. 正常流程和异常流程

写程序时,通常都会先考虑一种最理想的情况:用户输入正确、文件一定存在、网络一定正常、数据格式一定符合要求……这叫正常流程。但真实世界常常会有意外:用户输错数据、磁盘文件被删掉、数据库连接失败、服务器超时、某个对象本应有值但实际是 null。这些不在正常流程里的情况,就属于异常流程。

2. 异常处理的意义

可以把异常处理理解成:提前给程序准备“出问题时的备用方案”。比如:文件打不开,就提示“文件不存在”;用户输入错误,就提示“请输入合法数字”;网络断开,就提示“连接失败,请稍后重试”。这就是异常机制在程序设计中的现实意义。

三、Ja va 中的异常体系

1. 所有异常和错误的顶层父类:Throwable

在Ja va中,所有可以被“抛出”的问题对象,最终都继承自 Throwable。也就是说,Throwable 是Ja va异常体系的根类。它下面主要分为两大类:ErrorException

结构可以简单画成:

Throwable
├── Error
└── Exception

2. Error

Error 表示比较严重的问题,通常不是程序员通过普通代码就能轻易处理的。例如:StackOverflowError(栈溢出)、OutOfMemoryError(内存溢出)。这些问题通常说明程序本身存在严重缺陷,或者运行环境资源出现了问题。在一般开发中,Error 通常不作为常规异常处理对象。

3. Exception

Exception 才是我们平时编程中最常处理的一类异常。例如:数组越界、空指针、文件不存在、类型转换错误、输入输出失败等等。这些异常大多数都和程序逻辑、输入数据、外部资源有关。

四、异常的分类

在Ja va中,Exception 又可以进一步分成两大类:编译时异常和运行时异常。

1. 编译时异常

编译时异常也叫受查异常(Checked Exception)。这类异常的特点是:编译器要求程序员必须显式处理,否则代码不能通过编译。例如:

FileInputStream fis = new FileInputStream("a.txt");

这里可能会出现文件找不到的问题,所以编译器会要求你:要么 try-catch,要么 throws,否则编译报错。常见的编译时异常有:IOExceptionFileNotFoundExceptionSQLExceptionClassNotFoundException 等。

2. 运行时异常

运行时异常也叫非受查异常,通常是 RuntimeException 及其子类。这类异常的特点是:编译器不强制要求处理,但运行时可能出问题。例如:

int[] arr = {1, 2, 3};
System.out.println(arr[5]);

编译器不会阻止你,因为语法合法。但运行时会抛出 ArrayIndexOutOfBoundsException。常见的运行时异常有:NullPointerExceptionArithmeticExceptionArrayIndexOutOfBoundsExceptionClassCastExceptionNumberFormatException 等。

3. 二者区别总结

编译时异常

  • 编译器强制处理
  • 不处理就无法通过编译
  • 通常和外部资源、环境相关

运行时异常

  • 编译器不强制处理
  • 运行时才会暴露问题
  • 通常和代码逻辑错误有关

五、常见运行时异常

这一部分是学习异常时必须重点掌握的内容,因为这些异常在平时做题和写程序时最常见。

1. NullPointerException

空指针异常。示例:

String str = null;
System.out.println(str.length());

原因:str 没有真正指向对象,却调用了对象的方法。这是Ja va初学阶段最常见的异常之一。

2. ArithmeticException

算术异常。示例:

int a = 10;
int b = 0;
System.out.println(a / b);

原因:整数除以0。

3. ArrayIndexOutOfBoundsException

数组下标越界异常。示例:

int[] arr = {1, 2, 3};
System.out.println(arr[3]);

原因:访问了数组有效范围之外的下标。

4. StringIndexOutOfBoundsException

字符串下标越界异常。示例:

String s = "abc";
System.out.println(s.charAt(5));

5. ClassCastException

类型转换异常。示例:

Object obj = "hello";
Integer num = (Integer) obj;

原因:实际类型和强制转换目标类型不匹配。

6. NumberFormatException

数字格式异常。示例:

String s = "abc";
int num = Integer.parseInt(s);

原因:字符串内容不能转换为数字。

六、常见编译时异常

1. IOException

输入输出异常,是很多IO类操作中常见的父异常类型。例如:读文件失败、写文件失败、流关闭失败等。

2. FileNotFoundException

文件未找到异常。示例:

import ja va.io.FileInputStream;
public class Test {
    public static void main(String[] args) throws Exception {
        FileInputStream fis = new FileInputStream("abc.txt");
    }
}

如果文件不存在,就会出现该异常。

3. SQLException

数据库操作异常。例如:SQL语句错误、数据库连接失败、查询执行异常等。

4. ClassNotFoundException

类未找到异常。常出现在反射机制或动态加载类的场景中。

七、异常出现后程序会怎样

1. 默认情况:程序中断

如果异常出现后没有被处理,程序通常会停止执行,在控制台输出异常信息,给出异常类型和出错位置。例如:

public class Test {
    public static void main(String[] args) {
        int a = 10 / 0;
        System.out.println("这句话不会执行");
    }
}

运行后:异常被抛出,程序终止,后面的输出语句不会执行。

2. 异常信息一般包括什么

异常信息通常包括:异常类型、错误原因说明、调用栈信息。例如:

Exception in thread "main" ja va.lang.ArithmeticException: / by zero
    at Test.main(Test.ja va:3)

这说明异常类型是 ArithmeticException,原因是 / by zero,错误发生在 Test.ja va 第3行。

八、try-catch异常捕获机制

1. 基本作用

try-catch 用来捕获并处理异常。语法格式:

try {
    // 可能发生异常的代码
} catch (异常类型 变量名) {
    // 异常处理代码
}

2. 示例

public class Test {
    public static void main(String[] args) {
        try {
            int a = 10 / 0;
            System.out.println("try 中的后续语句");
        } catch (ArithmeticException e) {
            System.out.println("出现了算术异常");
        }
        System.out.println("程序继续执行");
    }
}

输出结果说明:10 / 0 出现异常,try 中异常后面的代码不再执行,进入 catch,异常被处理后,程序继续执行 catch 后面的代码。

3. 执行流程

没有异常

  • 执行 try
  • 跳过 catch
  • 继续往后执行

有异常

  • 执行 try
  • 发生异常,后续 try 代码停止
  • 找匹配的 catch
  • 执行对应 catch
  • 再继续往后执行

九、多个catch

一个 try 后面可以跟多个 catch,用于处理不同类型的异常。示例:

public class Test {
    public static void main(String[] args) {
        try {
            String s = null;
            System.out.println(s.length());
        } catch (ArithmeticException e) {
            System.out.println("算术异常");
        } catch (NullPointerException e) {
            System.out.println("空指针异常");
        } catch (Exception e) {
            System.out.println("其他异常");
        }
    }
}

1. 匹配原则

异常会按照 catch 的顺序,从上到下匹配。一旦匹配成功,后面的 catch 不再执行。

2. 注意顺序

如果写成:

catch (Exception e) { }
catch (NullPointerException e) { }

这通常是错误的,因为 Exception 是父类,它已经把后面的子类异常全接住了。因此,多个 catch 的顺序通常应该是:子类异常在前,父类异常在后

十、finally语句块

1. 基本作用

finally 用于表示:无论是否发生异常,通常都要执行的代码。语法:

try {
    // 可能异常的代码
} catch (异常类型 e) {
    // 异常处理
} finally {
    // 一般都会执行
}

2. 典型用途

最常见的用途是资源释放,例如:关闭文件流、关闭数据库连接、关闭网络连接等。因为这些资源不管程序有没有出错,最后通常都应该关闭。

3. 示例

public class Test {
    public static void main(String[] args) {
        try {
            int a = 10 / 0;
        } catch (ArithmeticException e) {
            System.out.println("捕获到异常");
        } finally {
            System.out.println("finally 执行");
        }
    }
}

输出中 finally 仍然会执行。

4. finally一定执行吗

一般情况下会执行,但并不是绝对的。例如下面情况可能不会执行:程序在执行前被强制终止、调用了 System.exit(0);、进程被操作系统直接杀死。所以更准确地说:在正常JVM执行流程下,finally 通常都会执行。

十一、try-catch-finally的完整形式

完整写法:

try {
    // 可能发生异常的代码
} catch (异常类型1 e) {
    // 处理方式1
} catch (异常类型2 e) {
    // 处理方式2
} finally {
    // 最终执行代码
}

1. 组合关系

合法形式

  • try-catch
  • try-finally
  • try-catch-finally

非法形式

  • 只有 try
  • 只有 catch
  • 只有 finally

也就是说:try 后面必须跟 catchfinally,至少一个。

十二、异常对象常用方法

catch 中,捕获到的异常其实是一个对象。例如:

catch (Exception e) {
}

这里的 e 就是异常对象。

1. printStackTrace()

打印完整异常信息和调用栈。这是调试时最常用的方法之一。

e.printStackTrace();

2. getMessage()

获取异常描述信息。

System.out.println(e.getMessage());

3. toString()

返回异常类名和异常信息。

System.out.println(e.toString());

4. 示例

try {
    int a = 10 / 0;
} catch (Exception e) {
    System.out.println(e.getMessage());
    System.out.println(e.toString());
    e.printStackTrace();
}

十三、throw关键字

1. 作用

throw 用于手动抛出一个异常对象。语法:

throw new 异常类名("异常信息");

2. 示例

public class Test {
    public static void main(String[] args) {
        int age = -1;
        if (age < 0) {
            throw new RuntimeException("年龄不能为负数");
        }
    }
}

这表示程序员主动认为这里是非法情况,于是主动抛出异常。

3. 使用场景

当程序发现某些条件不符合要求时,可以主动中断流程并抛出异常。例如:参数非法、数据状态不正确、业务条件不满足等。

十四、throws关键字

1. 作用

throws 用于写在方法声明后面,表示:当前方法可能抛出哪些异常,交给调用者处理。语法:

返回值类型 方法名(参数列表) throws 异常类名1, 异常类名2 {
}

2. 示例

import ja va.io.FileInputStream;
import ja va.io.FileNotFoundException;
public class Test {
    public static void readFile() throws FileNotFoundException {
        FileInputStream fis = new FileInputStream("a.txt");
    }
}

这表示:readFile() 方法内部可能抛出 FileNotFoundException,但当前方法自己不处理,交给调用该方法的人去处理。

3. throw和throws的区别

这是常考点。

throw

  • 用在方法内部
  • 后面跟异常对象
  • 表示“抛出一个具体异常”

throws

  • 用在方法声明处
  • 后面跟异常类型
  • 表示“声明可能抛出哪些异常”

十五、方法重写与异常

在继承中,子类重写父类方法时,也会涉及异常规则。

1. 基本原则

子类重写父类方法时,抛出的检查异常不能比父类更大。也就是说,子类不能随意扩大异常范围。

2. 原因

因为父类引用调用方法时,编译器是按照父类方法声明来判断异常的。如果子类突然抛出一个更大的受查异常,会破坏原有约定。

3. 初学阶段怎么记

可以先记一句:重写方法时,子类抛出的受查异常范围不能超过父类。

十六、自定义异常

很多时候,Ja va自带的异常类并不能完全表达我们的业务含义,这时就可以自定义异常类。

1. 为什么要自定义异常

例如系统中有这样的业务规则:用户年龄不能小于0、注册时用户名不能为空、转账金额必须大于0。如果全部都直接抛 throw new RuntimeException("出错了");,虽然也能用,但语义不清晰。如果自定义异常类,含义会更明确。

2. 自定义异常的方式

通常可以继承 Exception(表示受查异常)或 RuntimeException(表示运行时异常)。

3. 示例:自定义运行时异常

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

使用:

public class Test {
    public static void main(String[] args) {
        int age = -1;
        if (age < 0) {
            throw new AgeIllegalException("年龄不能小于 0");
        }
    }
}

这样程序的异常含义就更明确了。

4. 示例:自定义受查异常

class LoginException extends Exception {
    public LoginException() {
    }
    public LoginException(String message) {
        super(message);
    }
}

方法声明中:

public void login(String password) throws LoginException {
    if (!"123456".equals(password)) {
        throw new LoginException("密码错误");
    }
}

十七、finally与return的关系

这是一个比较经典、也比较容易出错的点。

1. 先看示例

public class Test {
    public static int test() {
        try {
            return 10;
        } finally {
            System.out.println("finally 执行");
        }
    }
}

结果:finally 还是会执行。

2. 为什么会这样

因为 return 并不是一写出来就立刻彻底结束。在方法真正返回前,JVM还会去执行 finally

3. 更复杂的情况

public class Test {
    public static int test() {
        try {
            return 10;
        } finally {
            return 20;
        }
    }
}

最终返回值是20。所以:不建议在 finally 中写 return,因为这样会让程序逻辑变得很混乱,也可能掩盖原本的异常或返回结果。

十八、JDK7 之后的资源自动关闭

在早期写法中,IO流通常这样处理:

FileInputStream fis = null;
try {
    fis = new FileInputStream("a.txt");
} catch (Exception e) {
    e.printStackTrace();
} finally {
    if (fis != null) {
        try {
            fis.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这种写法很安全,但比较繁琐。从JDK7开始,可以使用 try(资源对象声明) 语法:

import ja va.io.FileInputStream;
public class Test {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("a.txt")) {
            System.out.println("文件打开成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

1. 优点

  • 代码更简洁
  • 自动关闭资源
  • 更不容易遗漏释放操作

2. 前提

资源对象需要实现 AutoCloseable 接口,很多流类已经实现了。

十九、异常处理中常见错误

1. 抓了异常却什么也不做

例如:catch (Exception e) {},这叫“吞异常”。程序看似没报错,但真实问题被隐藏了,后续非常难排查。

2. 一上来就catch(Exception)

虽然这样写不一定错,但如果不加区分地全部捕获,会让异常处理过于粗糙。很多时候更应该优先捕获更具体的异常类型。

3. 把运行时异常全靠try-catch掩盖

例如明明应该先判断空值,却直接用 try-catch 去包住空指针。这种写法不优雅,也说明代码逻辑前置校验没做好。

4. 在finally中写复杂逻辑

finally 本来应该用于收尾、释放资源、执行必要清理,不适合写太复杂的业务逻辑。

5. 自定义异常时语义不清

如果异常名和异常信息写得很含糊,例如 MyException"出错了",那即使自定义了,也没有实际价值。异常类名和异常提示应该尽量表达真实问题。

二十、什么时候该处理,什么时候该抛出

这是实际开发中很重要的一个问题。

1. 当前方法能解决,就处理

例如:用户输入错误,可以在当前界面提示重新输入;文件路径错误,可以在当前层提示用户重新选择。这类问题当前层能消化,就可以直接处理。

2. 当前方法解决不了,就往上抛

例如底层工具方法只负责读取文件,它自己并不知道出错后是重试、还是提示用户、还是终止流程。这时它更适合把异常抛给上层,让更高层决定如何处理。

3. 一般原则

可以记住一句:谁更清楚如何处理这个问题,谁就应该处理异常。

二十一、异常机制和程序健壮性

一个优秀程序的重要特点之一,就是“健壮”。所谓健壮,指的是:面对异常输入不轻易崩溃、能给出合理提示、能保留必要日志、能保证重要资源被正确释放。异常机制正是实现程序健壮性的重要基础。在开发中,表单提交要校验数据、文件上传要校验格式、读取配置要考虑文件缺失、网络请求要考虑超时和失败,这些都需要异常思维,而不是只写“理想路径”。

二十二、学习异常时最容易混淆的几个点

1. throw和throws

  • throw:真正抛异常对象
  • throws:声明可能抛出异常类型

2. 编译时异常和运行时异常

  • 编译时异常:编译器强制处理
  • 运行时异常:编译器不强制,但运行时会出问题

3. final、finally、finalize

这是三个完全不同的词。

final

  • 修饰类、方法、变量

finally

  • 异常处理中的最终执行块

finalize

  • Object 类中的方法,和垃圾回收相关
  • 现代开发中基本不作为重点使用

这三者千万不要混淆。

二十三、学习建议

1. 不要只背概念,要亲手制造异常

建议自己动手写下面这些小例子:除0异常、数组越界异常、空指针异常、类型转换异常、文件找不到异常。亲自看到异常信息,比单纯看书更容易记住。

2. 多比较“处理前”和“处理后”

例如:不加 try-catch 时程序怎样,加了 try-catch 后程序怎样。这样才能真正理解异常机制的作用。

3. 重点抓住执行流程

异常这章最容易出错的地方,不是概念,而是流程。一定要弄清:异常在哪里产生、try 中后面代码还会不会执行、会进入哪个 catchfinally 会不会执行、方法到底是处理了还是抛出了异常。

4. 先掌握常见异常,再学自定义异常

学习顺序建议:

  1. 常见运行时异常
  2. try-catch-finally
  3. throw / throws
  4. 编译时异常
  5. 自定义异常

这样更顺。

二十四、本章知识总结

1. 异常的本质

异常表示程序在运行过程中间出现的不正常情况。它不是单纯的“报错信息”,而是一种完整的错误处理机制。

2. 异常体系

Ja va异常体系顶层是 Throwable,下面分为 ErrorException

3. 异常分类

Exception 主要分为编译时异常和运行时异常。其中,编译时异常要求强制处理,运行时异常通常反映代码逻辑问题。

4. 异常处理语法

最常见的语法有:trycatchfinallythrowthrows

5. 关键区别

  • throw:抛出具体异常对象
  • throws:声明方法可能抛出的异常类型

6. 开发中的重点

  • 不要吞异常
  • 能处理就处理,不能处理就抛出
  • 优先捕获具体异常
  • 注意资源释放
  • 必要时自定义异常

二十五、结语

Ja va中的异常机制,是连接“程序能运行”和“程序能稳定运行”之间的一座桥梁。很多初学者刚开始学异常时,会觉得它只是几个关键字和一些报错信息,但实际上,异常体现的是一种更完整的编程思维:不只写正常情况,还要考虑错误情况;不只让代码跑起来,还要让代码在出问题时保持可控。真正优秀的程序,并不是从不出错,而是在出错时仍然能够给出明确信息、保证资源安全、降低故障影响、方便后续排查。因此,学习异常这一章时,不能只把它当成一个知识点,而要把它看成Ja va程序设计中非常关键的一种“防御性思维”。只有真正掌握了异常机制,后面学习IO、集合、多线程、数据库、网络编程和项目开发时,代码才会更加规范,也更加稳定。

二十六、附:常见语法速查表

1. try-catch

try {
    // 可能出异常的代码
} catch (Exception e) {
    e.printStackTrace();
}

2. try-catch-finally

try {
    // 业务代码
} catch (Exception e) {
    e.printStackTrace();
} finally {
    System.out.println("收尾操作");
}

3. throw

throw new RuntimeException("参数非法");

4. throws

public void test() throws IOException {
}

5. 自定义异常

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

6. 自动关闭资源

try (FileInputStream fis = new FileInputStream("a.txt")) {
    // 使用资源
}

到此,Ja va中所有的语法部分已经介绍完毕,下一章我们将介绍Ja va中的常用类。

本文转载于:https://www.jb51.net/program/368139v4l.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注