Java 异常

Java 异常处理

异常的概念与本质

异常是程序运行过程中发生的非正常终止事件,是代表程序错误或意外情况的对象。它会打断程序的正常执行流程,若不处理会导致程序崩溃。

例如除数为0时,程序会抛出ArithmeticException,直接终止运行:

package com.exception_io;

import java.util.Scanner;

/**
 * @author Jing61
 */
public class ExceptionDemo {
    public static void main(String[] args) {
        System.out.println("请输入两个数:");
        Scanner scanner = new Scanner(System.in);
        int number1 = scanner.nextInt();
        int number2 = scanner.nextInt();
        int result = number1 / number2;
        System.out.printf("%d / %d = %d", number1, number2, result);
    }
    
}

如果除数为0,程序会非正常终止。

异常的核心处理原则

不建议在方法内部直接终止程序(如System.exit(1)),应通过抛出异常将错误情况传递给调用者,由调用者决定如何处理:

package com.exception_io;

import java.util.Scanner;

/**
 * @author Jing61
 */
public class ExceptionDemo {
    public static void main(String[] args) {
        System.out.println("请输入两个数:");
        Scanner scanner = new Scanner(System.in);
        int number1 = scanner.nextInt();
        int number2 = scanner.nextInt();
        int result = number1 / number2;
        System.out.printf("%d / %d = %d", number1, number2, result);
    }

    public static int divide(int number1, int number2) {
        // 不推荐直接终止程序
        // if(number2 == 0) System.exit(1); 
        // 抛出异常,将错误传递给调用者
        if (number2 == 0) {
            throw new ArithmeticException("除数不能为0");
        }
        // 抛出异常后,后续代码不会执行
        return number1 / number2;
    }
}

异常的体系结构

Java 中所有异常的根类是java.lang.Throwable,其下分为两大子类:

Error(错误)

  • 属于 JVM 系统内部错误,与 JVM 或系统资源相关,程序无法处理
  • 不应该试图捕获,通常是严重故障(如内存溢出、栈溢出),程序应直接终止。
  • 示例:StackOverflowError(栈溢出)、OutOfMemoryError(内存溢出)。

Exception(异常)

  • 程序运行时出现的错误,程序可以处理,是异常处理的核心对象。
  • 分为两大类:
    • 非运行时异常(必检异常):编译时必须处理(捕获或声明抛出),如IOExceptionFileNotFoundExceptionInterruptedException
    • 运行时异常(免检异常):编译时无需处理,运行时才可能抛出,如NullPointerExceptionArrayIndexOutOfBoundsExceptionClassCastExceptionNumberFormatExceptionArithmeticException

异常体系结构示意图:

Throwable
├─ Error(系统错误,不可处理)
│  ├─ StackOverflowError
│  └─ OutOfMemoryError
└─ Exception(程序异常,可处理)
   ├─ 非运行时异常(必检)
   │  ├─ IOException
   │  ├─ FileNotFoundException
   │  └─ InterruptedException
   └─ 运行时异常(免检,RuntimeException子类)
      ├─ ArithmeticException(除数为0)
      ├─ NullPointerException(空指针)
      ├─ ArrayIndexOutOfBoundsException(数组越界)
      ├─ ClassCastException(类型转换错误)
      └─ NumberFormatException(数字格式错误)

异常处理机制

Java 提供try-catchtry-catch-finallythrowsthrow等关键字处理异常,核心是“捕获异常”或“传递异常”。

try-catch:捕获并处理异常

try块包裹可能抛出异常的代码,catch块捕获指定类型的异常并处理,避免程序崩溃。

示例:捕获除数为0的异常

package com.exception_io;

import java.util.Scanner;

/**
 * @author Jing61
 */
public class ExceptionDemo {
    public static void main(String[] args) {
        System.out.println("请输入两个数:");
        Scanner scanner = new Scanner(System.in);
        int number1 = scanner.nextInt();
        int number2 = scanner.nextInt();
        
        try {
            // 尝试执行可能抛出异常的代码
            int result = divide(number1, number2);
            System.out.printf("%d / %d = %d", number1, number2, result);
        } catch (ArithmeticException e) {
            // 捕获ArithmeticException并处理
            System.out.println("除数不能为0");
        }
    }

    public static int divide(int number1, int number2) {
        if (number2 == 0) {
            throw new ArithmeticException("除数不能为0");
        }
        return number1 / number2;
    }
}

当然也可以捕获多个异常 try-catch(exception1)-catch(exception2)-...。

try-catch-finally:异常处理+资源清理

finally块中的代码无论是否发生异常,都会执行,常用于清理资源(如关闭流、释放锁)。

示例:添加finally块

package com.exception_io;

import java.util.Scanner;

/**
 * @author Jing61
 */
public class ExceptionDemo {
    public static void main(String[] args) {
        System.out.println("请输入两个数:");
        Scanner scanner = new Scanner(System.in);
        int number1 = scanner.nextInt();
        int number2 = scanner.nextInt();
        
        try { 
            // 尝试执行代码
            int result = divide(number1, number2);
            System.out.printf("%d / %d = %d", number1, number2, result); 
        } catch (ArithmeticException e) {
            // 发生异常时执行
            System.out.println("除数不能为0"); 
        } finally {
            // 无论是否异常,都会执行
            System.out.println("程序结束"); 
        }
    }

    public static int divide(int number1, int number2) {
        if (number2 == 0) {
            throw new ArithmeticException("除数不能为0");
        }
        return number1 / number2;
    }
}

finally 的特殊规则

  • finally块会优先于trycatch中的return执行,trycatch中的return会被“挂起”。
  • finally块中有return,则直接返回finally的结果,覆盖try/catchreturn值。

示例:finally 对 return 的影响

public static int test() {
    int i = 0;
    try {
        int a = 1 / 0; // 抛出异常
        return i++; // 不会执行
    } catch (Exception e) {
        return i++; // i变为1,return被挂起
    } finally {
        return ++i; // i变为2,直接返回
    }
}
// 最终返回值:2

多重异常:捕获多种异常类型

一个try可以搭配多个catch块,分别处理不同类型的异常,也支持嵌套try-catch

示例:处理输入不合法和除数为0两种异常

package com.exception_io;

import java.util.InputMismatchException;
import java.util.Scanner;

/**
 * @author Jing61
 */
public class ExceptionDemo {
    public static void main(String[] args) {
        System.out.println("请输入两个数:");
        Scanner scanner = new Scanner(System.in);
        
        try {
            // 可能抛出InputMismatchException(输入非数字)
            int number1 = scanner.nextInt();
            int number2 = scanner.nextInt();
            
            try { 
                // 可能抛出ArithmeticException(除数为0)
                int result = divide(number1, number2);
                System.out.printf("%d / %d = %d", number1, number2, result);
            } catch (ArithmeticException e) {
                System.out.println("除数不能为0");
            } finally {
                System.out.println("程序结束");
            }
        } catch (InputMismatchException e) {
            System.out.println("请输入数字");
        }
    }

    public static int divide(int number1, int number2) {
        if (number2 == 0) {
            throw new ArithmeticException("除数不能为0");
        }
        return number1 / number2;
    }
}

throws:声明异常

对于非运行时异常(必检异常),方法若不处理,必须在方法签名上用throws声明,告知调用者需处理该异常。

示例:声明并传递异常

package com.exception_io;

import java.util.Scanner;

/**
 * @author Jing61
 */
public class ExceptionDemo {
    // main方法声明抛出Exception,由JVM处理
    public static void main(String[] args) throws Exception {
        System.out.println("请输入:");
        Scanner scanner = new Scanner(System.in);
        int number = scanner.nextInt();
        
        try {
            test(number);
        } catch (Exception e) {
            // 获取异常信息
            System.out.println(e.getMessage()); // 异常描述信息
            e.getCause(); // 异常发生的根本原因
            e.printStackTrace(); // 打印异常堆栈(调试用)
            e.getStackTrace(); // 获取堆栈追踪信息
        }
    }

    // 声明抛出Exception,由调用者处理
    public static void test(int number) throws Exception{
        if(number < 0) {
            throw new Exception("参数不能小于0");
        }
        System.out.println("参数大于0,参数:" + number);
    }
}

throw:主动抛出异常

在方法内部使用throw关键字主动创建并抛出异常对象,通常用于自定义错误条件(如参数校验失败)。

语法:throw new 异常类(异常信息);

示例:主动抛出参数非法异常

public static void checkAge(int age) {
    if (age < 0 || age > 150) {
        throw new IllegalArgumentException("年龄必须在0-150之间");
    }
}

自定义异常

当 JDK 内置异常无法满足业务需求时,可自定义异常类,通常继承Exception(非运行时异常)或RuntimeException(运行时异常)。

自定义异常步骤

  1. 继承ExceptionRuntimeException
  2. 提供构造方法(通常调用父类构造方法传递异常信息);
  3. 在业务逻辑中抛出并处理自定义异常。

示例:自定义半径非法异常

自定义异常类

package com.exception_io;

/**
 * 自定义异常:非法半径异常
 * 继承Exception(非运行时异常,必检),若继承RuntimeException则为运行时异常(免检)
 * @author Jing61
 */
public class InvalidRadiusException extends Exception{
    private double radius;

    // 构造方法,传递异常信息
    public InvalidRadiusException(double radius) {
        super("Invalid radius: " + radius); // 调用父类构造方法
        this.radius = radius;
    }

    // 获取非法半径(可选)
    public double getRadius() {
        return radius;
    }
}

在业务类中使用

package com.exception_io;

/**
 * 圆类:使用自定义异常校验半径
 * @author Jing61
 */
public class Circle {
    private double radius;

    // 构造方法声明抛出自定义异常
    public Circle(double radius) throws InvalidRadiusException{
        if (radius < 0) {
            throw new InvalidRadiusException(radius); // 主动抛出自定义异常
        }
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }

    // setter方法声明抛出自定义异常
    public void setRadius(double radius) throws InvalidRadiusException {
        if (radius < 0) {
            throw new InvalidRadiusException(radius);
        }
        this.radius = radius;
    }
}

测试自定义异常

package com.exception_io;

/**
 * 测试自定义异常
 * @author Jing61
 */
public class CircleTest {
    public static void main(String[] args) {
        try {
            Circle circle = new Circle(-1); // 传入非法半径,触发异常
            System.out.println(circle.getRadius());
        } catch (InvalidRadiusException e) {
            // 处理自定义异常
            e.printStackTrace();
            System.out.println("非法半径:" + e.getRadius());
        }
    }
}

try-with-resources:自动资源管理

用于自动关闭实现AutoCloseable接口的资源(如InputStreamBufferedReaderScanner等),无需手动在finally中关闭,避免资源泄露,比如Scanner就可以不用通过.close()释放,会自动释放。

语法格式

try (资源声明1; 资源声明2; ...) {
    // 使用资源的代码
} catch (异常类型 e) {
    // 异常处理
}

示例1:自动关闭文件读取流

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class FileReadUtil {
    private static final Logger logger = LoggerFactory.getLogger(FileReadUtil.class);

    public static String readFile(String filePath) throws FileReadException {
        // BufferedReader实现AutoCloseable,会自动关闭
        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            StringBuilder content = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                content.append(line).append("\n");
            }
            return content.toString().trim();
        } catch (java.io.FileNotFoundException e) {
            logger.error("文件未找到,路径:{}", filePath, e);
            // 包装为自定义异常,保留异常链
            throw new FileReadException("FILE_NOT_FOUND", "文件不存在:" + filePath, e);
        } catch (IOException e) {
            logger.error("文件读取IO异常,路径:{}", filePath, e);
            throw new FileReadException("IO_ERROR", "读取文件失败:" + e.getMessage(), e);
        }
    }
}

// 自定义文件读取异常
class FileReadException extends Exception {
    private String errorCode;

    public FileReadException(String errorCode, String message, Throwable cause) {
        super(message, cause);
        this.errorCode = errorCode;
    }

    public String getErrorCode() {
        return errorCode;
    }
}

示例2:自动关闭Scanner

package com.exception_io;

import java.util.Scanner;

public class TryWithResourcesDemo {
    public static void main(String[] args) {
        // Scanner实现AutoCloseable,自动关闭,无需调用scanner.close()
        try (Scanner scanner = new Scanner(System.in)) {
            int number = scanner.nextInt();
            test(number);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }

    public static void test(int number) throws Exception {
        if (number < 0) {
            throw new Exception("参数不能小于0");
        }
        System.out.println("参数:" + number);
    }
}

异常处理最佳实践

  1. 只捕获已知异常,不兜底未知异常
    避免catch (Exception e)catch (Throwable t),未知异常(如NullPointerException)应向上传播,暴露程序问题,而非掩盖故障。

  2. 优先捕获具体异常类型
    针对性处理精确异常(如FileNotFoundException而非IOException),提高故障处理精准度(如文件不存在提示检查路径,权限不足提示授权)。

  3. 在finally中清理非自动关闭资源
    对于未实现AutoCloseable的资源(如自定义锁、Socket连接),需在finally中执行关闭/释放操作,确保资源不泄露。

  4. 优先使用try-with-resources管理资源
    对于流、连接等AutoCloseable实现类,用try-with-resources自动关闭,简化代码且避免手动关闭遗漏。

  5. 不“吞掉”异常,要么记录要么传播
    禁止catch (Exception e) {}空实现,需用日志框架记录异常详情(含堆栈),或包装为自定义异常重新抛出,便于问题追溯。

  6. 保留异常链,不丢失原始上下文
    重新抛出异常时,将原始异常作为cause传入(如throw new BusinessException("失败", e)),确保能追溯根本原因。

  7. 异常处理块不执行复杂逻辑
    catch/finally块仅执行日志记录、资源清理等必要操作,不包含业务逻辑(如数据计算),避免引发新异常。

  8. 自定义异常体现业务语义
    针对业务场景定义异常(如OrderNotFoundExceptionInsufficientBalanceException),包含错误码和描述,便于上层代码分流处理。

  9. 提前校验参数,减少异常抛出
    方法执行前校验入参(如if (filePath == null) throw new IllegalArgumentException("路径不能为空")),避免无效参数进入核心逻辑后抛出复杂异常。

  10. 合理控制异常传播范围
    异常应在“能处理的层级”捕获(如文件读取异常在工具类中包装),不盲目在每一层捕获后又抛出,减少传播开销。

posted @ 2025-11-08 10:33  Jing61  阅读(2)  评论(0)    收藏  举报