Skip to content

1. try-catch

1.1. 作用

  • try 块:包含可能会抛出异常的代码。当 Java 虚拟机执行 try 块中的代码时,如果遇到异常,它会立即停止执行 try 块中剩余的代码,并开始查找匹配的 catch 块来处理异常。
  • catch 块:用于捕获并处理 try 块中抛出的异常。每个 catch 块都声明了它能够处理的异常类型。当 try 块抛出异常时,Java 虚拟机会按顺序检查每个 catch 块,找到匹配异常类型的 catch 块后,就会执行该 catch 块中的代码。如果没有找到匹配的 catch 块,异常将被传递给上层调用栈,直到找到合适的处理程序或程序终止。

1.2. 示例

java
public class TryCatchExample {
    public static void main(String[] args) {
        try {
            int result = 10 / 0; // 这行代码会抛出ArithmeticException异常
            System.out.println("结果是: " + result);
        } catch (ArithmeticException e) {
            System.out.println("捕获到算术异常: " + e.getMessage());
        }
        System.out.println("程序继续执行...");
    }
}

输出内容:

text
异常了/ by zero
程序继续执行

说明:在这个例子中,try 块中的 10 / 0 会抛出 ArithmeticException 异常。一旦异常抛出,try 块中后续的代码(System.out.println("结果是: " + result);)将不会执行。程序会跳转到 catch 块,打印出捕获到的异常信息,然后继续执行 catch 块之后的代码(System.out.println("程序继续执行...");)。

2. 多个catch块

可以在一个 try 块后接多个 catch 块,分别捕获不同类型的异常。

2.1. 示例

java
public static void multipleCatch() {
    try {
        FileReader reader = new FileReader("nonexistentfile.txt");
        int data = reader.read();
        int result = 10 / 0;
    } catch (FileNotFoundException e) {
        System.out.println("文件未找到异常: " + e.getMessage());
    } catch (ArithmeticException e) {
        System.out.println("算术异常: " + e.getMessage());
    } catch (IOException e) {
        System.out.println("I/O 异常: " + e.getMessage());
    }
}

2.2. 异常捕获顺序

在这个示例中,异常捕获的顺序很重要。

  • FileNotFoundException 是 IOException 的子类,所以它的 catch 块必须放在 IOException 的 catch 块之前。如果顺序颠倒,FileNotFoundException 会被 IOException 的 catch 块捕获,导致无法针对文件未找到的情况进行特定处理。
  • ArithmeticException 与 IOException 没有继承关系,其 catch 块的位置在 IOException 之前或之后都可以。

所以,异常捕获的原则是先捕获子类,后捕获父类

2.3. 应用场景

  • 输入验证与处理:在处理用户输入时,可能会遇到多种类型的输入错误。例如,用户输入的字符串无法解析为数字(NumberFormatException),或者输入的内容不符合特定的业务规则(IllegalArgumentException)。通过多个 catch 块,可以分别处理这些不同类型的输入异常,向用户提供更准确的错误提示。
  • 资源操作:当涉及到文件、数据库连接等资源操作时,可能会因为资源不存在(FileNotFoundException 或 SQLException 中与连接相关的异常)、资源损坏(IOException)、操作不合法(如数据库事务中的约束违反 SQLIntegrityConstraintViolationException)等原因抛出不同的异常。使用多个 catch 块可以针对不同的资源操作异常进行相应的处理,比如记录日志、尝试重新连接资源等。

3. 异常向上传播

如果当前方法中没有合适的catch块来捕获异常,异常会被抛给调用该方法的上一层方法。这个过程会持续进行,就像异常在方法调用栈中 “冒泡” 一样,直到找到能够处理该异常的catch块。例如:

3.1. 异常沿着调用栈向上传播

java
public class ExceptionPropagation {
    public static void method1() {
        method2();
    }

    public static void method2() {
        int result = 10 / 0; // 抛出ArithmeticException异常
    }

    public static void main(String[] args) {
        try {
            method1();
        } catch (ArithmeticException e) {
            System.out.println("在main方法中捕获到算术异常: " + e.getMessage());
        }
    }
}

解释:在上述代码中,method2 抛出 ArithmeticException 异常,由于 method2 中没有 catch 块,异常被传递到 method1。method1 同样没有处理该异常的 catch 块,所以异常继续向上传播到 main 方法。在 main 方法中,try - catch 块捕获到了这个异常并进行处理。

3.2. 最终由JVM处理

如果异常一直传播到主线程(对于 Java 应用程序通常是main方法),且主线程也没有捕获该异常,那么 Java 虚拟机(JVM)会采取默认的处理方式。JVM 会在控制台打印异常的堆栈跟踪信息,显示异常发生的位置和调用路径,然后终止当前线程。如果该线程是程序的主线程,整个程序将会终止。

java
package com.one.pojian.codes;

public class Exception01 {
    public static void main(String[] args) {
        int a = 1 / 0;
        System.out.println("a = " + a);
    }
}

运行上述代码,控制台会输出类似以下的堆栈跟踪信息:

text
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at com.one.pojian.codes.Exception01.main(Exception01.java:9)

由于没有捕获该异常,JVM 打印了异常信息并终止了程序。

3.3. 异常处理最佳实践建议

  • 适当捕获异常:在编写代码时,应尽量在合适的位置捕获并处理异常,避免异常无限制地向上传播到 JVM。这样可以使程序更加健壮,防止意外终止,并能提供更好的用户体验。例如,在进行文件操作、数据库连接等可能抛出异常的操作时,要及时捕获并处理相关异常,如IOException、SQLException等。
  • 使用日志记录:在捕获异常时,除了进行相应的处理,还应使用日志记录工具(如java.util.logging、SLF4J等)记录异常信息,以便于调试和排查问题。这样即使异常没有导致程序崩溃,开发人员也能通过日志了解程序运行过程中出现的问题。
  • 避免捕获宽泛的异常:虽然可以使用catch (Exception e) 捕获所有类型的异常,但应尽量避免这样做,除非有充分的理由。因为捕获宽泛的异常可能会掩盖真正的问题,使得调试变得困难。应该针对具体的异常类型进行捕获和处理,以确保能够准确应对不同的错误情况。