优秀的编程知识分享平台

网站首页 > 技术文章 正文

(三)Java基础知识复习(异常处理)_java异常处理规则(新手必看)

nanyue 2025-09-01 10:28:15 技术文章 3 ℃

一、异常概述

异常,是指程序运行过程中发生错误,如果不能正确的处理异常会导致程序的运行被迫中断。Java 为我们提供了一套完整的异常处理机制来应对程序在运行过程中发生的各种异常。

异常的分类
Throwable 是异常的顶层父类,它的下面定义了 Java 中的两大异常,分别是 Error 和 Exception。

Error:指的是系统内部发生了严重的错误,这些错误通常与我们写的代码无关,我们不能处理这些错误,也不应该去处理。

Exception:指的是程序运行时发生的异常,合理的处理这些异常能够使我们的程序从错误的状态中恢复过来。

Throwable 的常用方法
打印异常的详细信息(包含了异常的类型、发生异常的原因以及抛出异常的位置)

public void printStackTrace()

获取异常的描述信息

public String getMessage()

我们通常说的异常都是 Exception,因为 Error 一旦发生我们不能处理,也不应该处理。

  • 编译期异常:
    又称受检异常(Checked Exception),这类异常在代码编译时就会进行检查,如果没有对受检异常进行处理将会编译失败。
  • 运行时异常:
    运行时异常(Runtime Exception)不会在代码编译时进行检查,这意味着我们不处理运行时异常,代码也能编译通过。但是,这类异常在代码运行时仍然可能发生,所以,我们在编码时应当要能预见这类异常发生的可能性,并对其做出适当的处理,避免程序被迫中断。

Java 中的异常体系结构如下:

  • Throwable 类:所有异常和错误的基类。
  • Error 类:表示系统级的错误,通常无法由程序处理,例如内存不足(OutOfMemoryError)。
  • Exception 类:表示程序中可以处理的异常情况。
  • RuntimeException 类:运行时异常,通常是编程错误导致的,例如数组下标越界(ArrayIndexOutOfBoundsException)。
  • Checked Exception:非运行时异常,必须通过编译器检查,例如文件未找到(FileNotFoundException)。

二、异常处理

异常处理的核心思想是将正常的程序流程与异常处理流程分离开来,使代码更加清晰和可维护。

异常的处理有抛出和捕获两种处理方案。

1.抛出异常\color{red}{抛出异常}抛出异常:当前方法无法处理异常时,可以选择将异常抛出,由当前方法的调用者来处理该异常。

//方法中抛出受检异常时,需要在方法的签名上用 throws 声明异常的类型
public static void read(String filename) throws FileNotFoundException {    
    if (filename == null) //throw 抛出一个运行时异常        
        throw new NullPointerException("参数不能为 null");    
    if (filename.isEmpty()) //throw 抛出一个受检异常        
        throw new FileNotFoundException("文件不存在");    
    
    //read ...
}

2.捕获异常\color{red}{捕获异常}捕获异常:当前方法知道如何处理异常,可以选择将异常捕获,并作出适当的处理。

public static Date parse(String val) {    
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");    
    try { 
        //将可能出现异常的代码写在 try 中,发生异常时,将被 catch 捕获        
        return sdf.parse(val);    
    } catch (ParseException e) {        
        e.printStackTrace(); 
        //捕获到异常后,可以做一些适当的处理         
        return null;    
    }
}

(1) try-catch 块

try-catch 块用于捕获和处理异常。基本语法结构如下:

try {
    // 可能抛出异常的代码
} catch (ExceptionType1 e1) {
    // 处理异常类型1
} catch (ExceptionType2 e2) {
    // 处理异常类型2
} finally {
    // 可选的,始终执行的代码块
}

例如,下面是一个简单的示例:

public class TryCatchExample {
    public static void main(String[] args) {
        try {
            int result = 10 / 0; // 这会引发 ArithmeticException
        } catch (ArithmeticException e) {
            System.out.println("捕获到算术异常:" + e.getMessage());
        }
    }
}

在上面的示例中,try 块中的代码试图执行一个除零操作,这会引发 ArithmeticException。该异常被 catch 块捕获,并输出相应的错误信息。

(2) finally 子句

finally 子句是异常处理机制中的一个重要组成部分,它用于执行一些清理操作,不管是否抛出异常,finally 块中的代码都会被执行。例如:

public class FinallyExample {
    public static void main(String[] args) {
        try {
            int[] array = new int[5];
            System.out.println(array[6]); // 这会引发 ArrayIndexOutOfBoundsException
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("捕获到数组越界异常:" + e.getMessage());
        } finally {
            System.out.println("无论是否发生异常,finally 块中的代码都会执行。");
        }
    }
}

在这个示例中,不管是否发生异常,finally 块中的代码都会被执行。

(3)使用Try-with-resources自动关闭资源

"Try-with-resources" 是 Java 7 引入的一种新的异常处理机制,它允许在 try 语句块中使用的资源被自动关闭。对于使用过 Java 的传统异常处理方式(即使用 try-catch-finally 来管理资源)的开发者来说,try-with-resources 提供了一种更简洁和安全的方式来管理资源关闭。

传统的资源管理

在 Java 7 之前,当使用诸如 FileInputStream 之类的资源时,必须显式地在 finally 块中关闭它们。例如:

private static void printFile() throws IOException {
    InputStream input = null;
    try {
        input = new FileInputStream("file.txt");
        int data = input.read();
        while (data != -1) {
            System.out.print((char) data);
            data = input.read();
        }
    } finally {
        if (input != null) {
            input.close();
        }
    }
}

在上面的代码中,资源关闭代码位于 finally 块中,这样即使 try 块中发生异常,资源也能被正确关闭。然而,这种做法容易出错,而且代码较冗长。

使用 try-with-resources,可以简化上述代码,并且自动管理资源的关闭:

private static void printFileJava7() throws IOException {
    try (FileInputStream input = new FileInputStream("file.txt")) {
        int data = input.read();
        while (data != -1) {
            System.out.print((char) data);
            data = input.read();
        }
    }
}

在这里,FileInputStream 的声明和初始化是在 try 语句中的括号里完成的。当 try 块结束时,无论是否抛出异常,FileInputStream 都会被自动关闭。这是因为 FileInputStream 实现了 AutoCloseable 接口。

多个资源的使用

可以在同一个 try-with-resources 语句中声明多个资源,所有这些资源会按照它们声明的逆序被关闭:

private static void printFileJava7() throws IOException {
    try (FileInputStream input = new FileInputStream("file.txt");
         BufferedInputStream bufferedInput = new BufferedInputStream(input)) {

        int data = bufferedInput.read();
        while (data != -1) {
            System.out.print((char) data);
            data = bufferedInput.read();
        }
    }
}

在这个例子中,BufferedInputStream 将在 FileInputStream 之前关闭,因为它是在后面声明的。

自定义 AutoCloseable 实现

你也可以自定义实现 AutoCloseable 接口的类,并在 try-with-resources 中使用:

public class MyAutoClosable implements AutoCloseable {

    public void doIt() {
        System.out.println("MyAutoClosable doing it!");
    }

    @Override
    public void close() throws Exception {
        System.out.println("MyAutoCloseable Closed!");
    }
}

private static void myAutoClosable() throws Exception {
    try (MyAutoClosable myAutoClosable = new MyAutoClosable()) {
        myAutoClosable.doIt();
    }
}

这里,MyAutoClosable 实现了 AutoCloseable 接口,确保在 try 块结束时调用 close() 方法。

三、自定义异常

有时,我们需要创建自己的异常类来表示特定的错误情况。自定义异常类需要继承自 Exception 或 RuntimeException。例如:

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

public class CustomExceptionExample {
    public static void main(String[] args) {
        try {
            validateAge(15);
        } catch (CustomException e) {
            System.out.println("捕获到自定义异常:" + e.getMessage());
        }
    }

    public static void validateAge(int age) throws CustomException {
        if (age < 18) {
            throw new CustomException("年龄必须大于或等于 18。");
        }
    }
}

在这个示例中,validateAge 方法检查年龄是否大于或等于 18,如果不满足条件,就抛出自定义的 CustomException。

四、异常处理的注意事项

  1. 避免捕获 RuntimeException:
    使用预检查方式防止 RuntimeException。仅在必要时捕获无法预检查的异常,例如 NumberFormatException。
  2. 使用 try-with-resources 关闭资源:
    关闭资源时,优先使用 try-with-resources,避免在 try 块中直接关闭资源。对于未实现 AutoCloseable 的资源,在 finally 块中关闭。
  3. 不要捕获 Throwable:
    捕获 Throwable 可能会掩盖程序无法处理的严重错误,例如 OutOfMemoryError。
  4. 记录异常信息:
    捕获异常时,确保记录异常信息,避免上线后无法查找问题。
  5. 不要同时记录和抛出异常:
    记录异常后再抛出会导致信息混乱。直接抛出或记录即可。
  6. 避免在 finally 块中使用 return:
    finally 中的 return 会覆盖 try 块中的 return,导致意外行为。
  7. 抛出具体异常而非 Exception:
    抛出具体异常可以更好地传达异常信息,帮助理解和处理。
  8. 捕获具体子类异常而非 Exception:
    捕获具体异常子类可以更精准地处理异常,避免捕获不需要处理的异常。
  9. 自定义异常时保留堆栈跟踪:
    保留原始异常的堆栈跟踪信息,便于调试。
  10. finally 块中不抛出异常:
    在 finally 中抛出异常会掩盖原始异常信息。
  11. 避免在生产环境中使用 printStackTrace():
    使用日志系统记录异常信息,避免性能和安全问题。
  12. 不处理的异常使用 try-finally:
    对于不处理的异常,使用 try-finally 进行必要的清理。
  13. “早抛出,晚捕获”原则:
    早抛出异常以便及时处理,晚捕获异常以获得更多上下文信息。
  14. 只抛出与方法相关的异常:
    抛出与方法相关的异常以提供有价值的信息。
  15. 避免使用异常进行流程控制:
    使用合适的控制结构代替异常进行流程控制。
  16. 验证输入以捕获早期异常:
    在进行数据库操作前,验证所有输入以避免不一致状态。
  17. 一个异常只能包含在一个日志中:
    确保异常信息在多线程环境中清晰可辨。
  18. 传递所有相关信息给异常:
    提供有用的异常信息和堆栈跟踪以便调试。
  19. 终止被中断的线程:
    在捕获 InterruptedException 时,终止当前线程的执行。
  20. 使用模板方法减少重复的 try-catch:
    将重复的异常处理逻辑提取为模板方法,减少代码重复性。

通过这些最佳实践,可以有效提高代码的健壮性和可维护性。在实际开发中,这些经验可能会帮助避免一些常见的异常处理问题。

最近发表
标签列表