第45章

Java throw和throws

深入学习Java中throw和throws关键字的使用方法,掌握异常的抛出和声明机制

学习目标

throw关键字详解

throw关键字用于在程序中主动抛出异常。当程序检测到错误条件时,可以使用throw语句创建并抛出一个异常对象。

基本语法

// 语法格式
throw new ExceptionType("异常信息");

// 示例
if (age < 0) {
    throw new IllegalArgumentException("年龄不能为负数: " + age);
}

throw的特点

  • 主动抛出:程序员主动创建并抛出异常对象
  • 立即终止:执行到throw语句时,当前方法立即终止
  • 异常对象:throw后面必须跟一个异常对象实例
  • 任何异常:可以抛出任何Throwable类型的异常

throw使用示例

public class ThrowExample {
    
    /**
     * 验证年龄是否合法
     */
    public static void validateAge(int age) {
        if (age < 0) {
            // 使用throw主动抛出异常
            throw new IllegalArgumentException("年龄不能为负数: " + age);
        }
        if (age > 150) {
            throw new IllegalArgumentException("年龄不能超过150岁: " + age);
        }
        System.out.println("年龄验证通过: " + age + "岁");
    }
    
    /**
     * 计算平方根
     */
    public static double calculateSquareRoot(double number) {
        if (number < 0) {
            throw new ArithmeticException("不能计算负数的平方根: " + number);
        }
        return Math.sqrt(number);
    }
    
    public static void main(String[] args) {
        try {
            validateAge(25);  // 正常执行
            validateAge(-5);  // 抛出异常
        } catch (IllegalArgumentException e) {
            System.out.println("捕获异常: " + e.getMessage());
        }
    }
}

throws关键字详解

throws关键字用于在方法签名中声明该方法可能抛出的异常类型。它告诉调用者这个方法可能会抛出哪些异常,调用者必须处理这些异常或继续向上抛出。

基本语法

// 语法格式
public void methodName() throws ExceptionType1, ExceptionType2 {
    // 方法体
}

// 示例
public void readFile(String filename) throws IOException {
    // 可能抛出IOException的代码
}

throws的特点

  • 声明异常:在方法签名中声明可能抛出的异常
  • 多个异常:可以声明多个异常类型,用逗号分隔
  • 强制处理:调用者必须处理或继续声明这些异常
  • 编译检查:编译器会检查异常是否被正确处理

throws使用示例

import java.io.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ThrowsExample {
    
    /**
     * 读取文件内容
     */
    public static String readFile(String filename) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(filename));
        StringBuilder content = new StringBuilder();
        String line;
        
        while ((line = reader.readLine()) != null) {
            content.append(line).append("\n");
        }
        
        reader.close();
        return content.toString();
    }
    
    /**
     * 解析日期字符串
     */
    public static Date parseDate(String dateStr, String pattern) throws ParseException {
        SimpleDateFormat formatter = new SimpleDateFormat(pattern);
        return formatter.parse(dateStr);
    }
    
    /**
     * 处理多个异常的方法
     */
    public static void performOperation(String operation) 
            throws IOException, ParseException, SQLException {
        
        switch (operation) {
            case "file":
                readFile("test.txt");
                break;
            case "date":
                parseDate("invalid-date", "yyyy-MM-dd");
                break;
            case "database":
                connectDatabase("", "user", "pass");
                break;
        }
    }
    
    public static void main(String[] args) {
        // 调用者必须处理异常
        try {
            String content = readFile("test.txt");
            System.out.println("文件内容: " + content);
        } catch (IOException e) {
            System.out.println("文件操作异常: " + e.getMessage());
        }
    }
}

throw vs throws 对比

特性 throw throws
位置 方法体内 方法签名中
作用 抛出异常 声明异常
数量 一次只能抛出一个 可以声明多个
语法 throw + 异常对象 throws + 异常类型
执行时机 运行时执行 编译时检查
后续代码 不会执行 正常执行

对比示例

public class ThrowVsThrowsExample {
    
    /**
     * 使用throw主动抛出异常的方法
     */
    public static void validateScore(int score) {
        // 使用throw主动抛出异常
        if (score < 0 || score > 100) {
            throw new IllegalArgumentException("分数必须在0-100之间: " + score);
        }
        System.out.println("分数有效: " + score);
    }
    
    /**
     * 使用throws声明可能抛出异常的方法
     */
    public static double safeDivide(double dividend, double divisor) throws ArithmeticException {
        if (divisor == 0) {
            throw new ArithmeticException("除数不能为零");
        }
        return dividend / divisor;
    }
    
    /**
     * 同时使用throw和throws的方法
     */
    public static String processText(String text, int maxLength) 
            throws IllegalArgumentException, RuntimeException {
        
        // 参数验证,使用throw抛出异常
        if (text == null) {
            throw new IllegalArgumentException("文本不能为null");
        }
        
        if (maxLength <= 0) {
            throw new IllegalArgumentException("最大长度必须大于0");
        }
        
        // 业务逻辑处理
        if (text.length() > maxLength) {
            if (maxLength < 10) {
                throw new RuntimeException("文本太长且最大长度太小,无法处理");
            }
            return text.substring(0, maxLength) + "...";
        }
        
        return text;
    }
}

异常传播机制

当方法抛出异常时,异常会沿着调用栈向上传播,直到被捕获处理或到达程序顶层。

public class ExceptionPropagationExample {
    
    /**
     * 方法链中的异常传播示例
     */
    public static void methodA(int value) throws Exception {
        System.out.println("方法A开始执行");
        methodB(value);  // 调用可能抛出异常的方法
        System.out.println("方法A执行完成");
    }
    
    public static void methodB(int value) throws Exception {
        System.out.println("方法B开始执行");
        methodC(value);  // 继续传播异常
        System.out.println("方法B执行完成");
    }
    
    public static void methodC(int value) throws Exception {
        System.out.println("方法C开始执行");
        if (value < 0) {
            throw new Exception("方法C: 值不能为负数 " + value);
        }
        System.out.println("方法C执行完成,值: " + value);
    }
    
    public static void main(String[] args) {
        try {
            methodA(5);   // 正常执行
            methodA(-1);  // 异常传播
        } catch (Exception e) {
            System.out.println("在main方法中捕获异常: " + e.getMessage());
        }
    }
}
💻 查看完整代码 - 在线IDE体验

异常处理最佳实践

异常处理原则

好的做法

// 尽早抛出异常
if (user == null) {
    throw new IllegalArgumentException("用户不能为null");
}

// 提供有意义的异常信息
throw new IllegalArgumentException(
    "年龄必须在0-150之间,当前值: " + age);

// 使用具体的异常类型
throw new FileNotFoundException("文件未找到: " + filename);

// 保留异常链
try {
    // 一些操作
} catch (IOException e) {
    throw new ServiceException("服务调用失败", e);
}
  • 尽早抛出异常,尽晚捕获异常
  • 提供有意义的异常信息
  • 使用具体的异常类型
  • 保留异常链,不丢失原始信息
  • 合理使用检查异常和运行时异常

应该避免的做法

// 忽略异常
try {
    riskyOperation();
} catch (Exception e) {
    // 什么都不做 - 错误做法
}

// 异常信息不明确
throw new Exception("错误");

// 过度使用检查异常
public void setAge(int age) throws InvalidAgeException {
    if (age < 0) throw new InvalidAgeException();
}

// 丢失异常信息
try {
    operation();
} catch (SpecificException e) {
    throw new RuntimeException("操作失败");
}
  • 忽略异常或空的catch块
  • 异常信息不明确或无意义
  • 过度使用检查异常
  • 丢失原始异常信息
  • 捕获过于宽泛的异常类型

异常类型选择指南

检查异常 vs 运行时异常

  • 检查异常(Checked Exception):必须在方法签名中声明或捕获处理,如IOException、SQLException
  • 运行时异常(Runtime Exception):不强制要求声明或捕获,如IllegalArgumentException、NullPointerException
  • 错误(Error):系统级错误,通常不应该捕获,如OutOfMemoryError、StackOverflowError

异常选择建议

  • 对于可恢复的错误,使用检查异常
  • 对于编程错误,使用运行时异常
  • 不要为简单的参数验证使用检查异常
  • 优先使用标准异常类型
  • 必要时创建自定义异常类

本章小结