Java基础快速入门: 异常处理机制完全指南

本文纲要

  1. 异常体系结构和异常分类
  2. 虚拟机默认处理异常方式
  3. throws 声明异常
  4. 声明异常注意事项
  5. throw 抛出异常
  6. try … catch 自己处理异常
  7. try … catch 常见问题
  8. Throwable 成员方法
  9. 异常小练习
  10. 自定义异常

异常体系结构和异常分类

当代码出现问题时,Java 程序会以异常的形式体现。例如:

  • 数组索引越界:ArrayIndexOutOfBoundsException
  • 空指针:NullPointerException
  • 日期解析错误:ParseException

在 Java 中,异常并非语法错误(如关键字拼写错误),而是一种特殊的对象体系。所有的异常类都继承自 Throwable,其下分为两大分支:

  • Error:严重问题,程序无法处理(如内存溢出),不需要我们关心。
  • Exception:程序本身可以处理的问题,是开发者关注的重点。Exception 又分为:
    • 运行时异常:RuntimeException 及其子类,如 NullPointerExceptionArrayIndexOutOfBoundsException,在编译期不强制处理。
    • 编译时异常:除 RuntimeException 之外的其他异常,如 ParseException,必须在编译期处理,否则代码无法通过编译。

Throwable

Error

Exception

RuntimeException

NullPointerException

ArrayIndexOutOfBoundsException

ArithmeticException

ParseException

IOException

演示不同类型的异常:

public class ExceptionDemo1 {
    public static void main(String[] args) throws ParseException {
        // 1. 运行时异常:数组索引越界 
        // int[] arr = {1, 2, 3, 4, 5};
        // System.out.println(arr[10]); // ArrayIndexOutOfBoundsException 
 
        // 2. 运行时异常:空指针 
        // String s = null;
        // System.out.println(s.equals("嘿嘿")); // NullPointerException 
 
        // 3. 编译时异常:日期解析异常 
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
        sdf.parse("2048-1月1日"); // ParseException 
    }
}

虚拟机默认处理异常方式

程序运行过程中,当某行代码出现异常时,JVM 会做以下事情:

  1. 创建对应的异常对象。例如 new ArrayIndexOutOfBoundsException()
  2. 检查是否有自己编写的异常处理代码。
  3. 如果没有,将异常交给本方法的调用者,最终交给虚拟机。
  4. JVM 默认处理异常会做两件事:
    • 将异常的类名、原因、位置等信息以红色字体输出在控制台。
    • 停止程序,异常位置之后的代码不再执行。
public class ExceptionDemo2 {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        System.out.println(arr[10]); // 这里抛出 ArrayIndexOutOfBoundsException 
 
        // 这行代码不会执行 
        System.out.println("嘿嘿嘿,我最帅");
    }
}

throws 声明异常

throws 关键字用于在方法声明处标识可能出现的异常,本身不处理异常,而是告知调用者该方法可能产生的异常,由调用者决定如何处理。

格式:

访问修饰符 返回值类型 方法名(参数列表) throws 异常类名 {
    // 方法体 
}
public class ExceptionDemo6 {
    public static void main(String[] args) throws ParseException {
        method1(); // 调用者没有处理,最终交给虚拟机 
        method2(); // 同样交给虚拟机 
    }
 
    // 声明运行时异常,throws 可以省略不写 
    private static void method1() /throws NullPointerException/ {
        int[] arr = null;
        for (int i = 0; i < arr.length; i++) { // 这里发生空指针异常 
            System.out.println(arr[i]);
        }
    }
 
    // 声明编译时异常,必须显式写出 
    private static void method2() throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
        sdf.parse("2048-10月10日");
    }
}

声明异常注意事项

编译时异常:因为编译阶段就会检查,所以必须在方法签名上显式声明,否则代码会报错。
运行时异常:在编译阶段不检查,throws 声明可以省略,即使不写,程序也能通过编译。

// 运行时异常可省略 throws NullPointerException 
private static void method1() {
    // ...
}
 
// 编译时异常必须保留 throws ParseException 
private static void method2() throws ParseException {
    // ...
}

throw 抛出异常

throw 用于在方法体内部手动抛出异常对象。一旦执行到throw,其后的代码将不再执行,异常会被交给调用者处理。

格式:

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

使用场景:
当方法参数非法,继续运行没有意义时,应当主动抛出异常,让方法提前结束,并告知调用者问题所在。

1 ) 演进:从输出提示到抛出异常

最初的写法(不推荐):非法数据时只打印一句提示,调用者无法感知是否真正成功。

private static void printArr(int[] arr) {
    if (arr == null) {
        System.out.println("参数不能为null"); // 调用者并不知道是否成功 
    } else {
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

改进:用 throw 抛出异常,明确告知调用者

public class ExceptionDemo8 {
    public static void main(String[] args) {
        int[] arr = null;
        printArr(arr); // 这里会接收到异常,目前尚未处理,最终交给 JVM 
    }
 
    private static void printArr(int[] arr) {
        if (arr == null) {
            throw new NullPointerException(); // 手动创建并抛出异常对象 
        } else {
            for (int i = 0; i < arr.length; i++) {
                System.out.println(arr[i]);
            }
        }
    }
}

2 ) 对比 throw 和 throws:

关键字位置后面跟作用
throws方法声明处异常类名声明异常,告知调用者可能发生
throw方法体内部异常对象手动抛出异常,结束方法

try … catch 自己处理异常

若希望异常发生后程序仍能继续运行,就需要自己捕获并处理异常

格式:

try {
    // 可能出现异常的代码 
} catch (异常类名 变量名) {
    // 出现异常后的处理代码 
}

示例:

public class ExceptionDemo9 {
    public static void main(String[] args) {
        int[] arr = null;
        try {
            printArr(arr);
        } catch (NullPointerException e) {
            System.out.println("参数不能为null");
        }
        System.out.println("嘿嘿嘿,我最帅!!!"); // 能够正常执行 
    }
 
    private static void printArr(int[] arr) {
        if (arr == null) {
            throw new NullPointerException();
        } else {
            for (int i = 0; i < arr.length; i++) {
                System.out.println(arr[i]);
            }
        }
    }
}

好处: 即使出现异常,程序不会终止,后面的代码仍可继续执行。

try … catch 常见问题

1 ) try 中没有遇到问题
会顺序执行 try 块内所有代码。
不会进入 catch 块。
执行完 try-catch 整体后,继续执行下面的代码。

2 ) try 中遇到问题
出现异常的代码之后的 try 块内代码将不再执行。
跳转到匹配的 catch 块中处理。
catch 执行完毕后,继续执行 try-catch 之后的代码。

3 ) 异常没有被捕获
如果 catch 中声明的异常类型与发生的异常不匹配,则相当于没有处理。
最终会交回给 JVM 默认处理:打印红色错误信息并终止程序。

4 ) 同时出现多个异常
可以写多个 catch 进行分别处理。
注意:如果多个异常存在子父类关系,父类异常的 catch 必须写在下方,否则子类的 catch 永远无法执行。

public class ExceptionDemo10 {
    public static void main(String[] args) {
        // 正确写法:子类异常在前,父类在后 
        try {
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入你的年龄");
            String line = sc.nextLine();
            int age = Integer.parseInt(line); // 可能产生 NumberFormatException 
            System.out.println(age);
            System.out.println(2 / 0);        // 可能产生 ArithmeticException 
        } catch (NumberFormatException e) {
            System.out.println("格式化异常出现了");
        } catch (ArithmeticException e) {
            System.out.println("数学运算异常出现了");
        }
        System.out.println("测试456");
 
        // 不推荐直接捕获 Exception 而没有针对性处理 
        /*
        try {
            // ...
        } catch (Exception e) {
            // 所有异常都用同一种方式处理,无法区分 
        }
        */
    }
}

Throwable 成员方法

所有异常类均继承自 Throwable,因此都可以调用以下三个方法:

方法说明
getMessage()返回异常的详细消息字符串
toString()返回异常的简短描述(类名 + 消息)
printStackTrace()将异常的完整堆栈跟踪以红色字体输出到控制台
public class ExceptionDemo11 {
    public static void main(String[] args) {
        try {
            int[] arr = {1, 2, 3, 4, 5};
            System.out.println(arr[10]); // 产生 ArrayIndexOutOfBoundsException 
        } catch (ArrayIndexOutOfBoundsException e) {
            // 方法一:获取消息 
            // String message = e.getMessage();
            // System.out.println(message);
 
            // 方法二:输出简短描述 
            // String s = e.toString();
            // System.out.println(s);
 
            // 方法三:打印红色错误堆栈,但程序继续执行 
            e.printStackTrace();
        }
        System.out.println("嘿嘿嘿"); // 仍然会执行 
    }
}

值得注意的是:printStackTrace() 虽然输出红色错误信息,但并不会终止程序,因此和 JVM 默认处理有本质区别。

异常小练习

需求: 键盘录入学生姓名和年龄,年龄必须介于 18~25 周岁,若不满足则要求重新录入,直到正确为止。

分析:

  1. 创建学生类 Student,在 setAge() 中加入数据校验,若不合法则抛出异常。
  2. 测试类中使用 try-catch 捕获可能出现的两种异常:数字格式异常、年龄超范围异常。
  3. 使用 while(true) 循环实现重复录入,直到数据合法后 break 跳出。
public class Student {
    private String name;
    private int age;
 
    // 构造器、getter、setter、toString 省略...
 
    public void setAge(int age) {
        if (age >= 18 && age <= 25) {
            this.age = age;
        } else {
            // 抛出自定义异常,让异常信息更见名知意 
            throw new AgeOutOfBoundsException("年龄超出了范围");
        }
    }
}
public class ExceptionDemo12 {
    public static void main(String[] args) {
        Student s = new Student();
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入姓名");
        String name = sc.nextLine();
        s.setName(name);
 
        while (true) {
            System.out.println("请输入年龄");
            String ageStr = sc.nextLine();
            try {
                int age = Integer.parseInt(ageStr);
                s.setAge(age);
                break; // 赋值成功,结束循环 
            } catch (NumberFormatException e) {
                System.out.println("请输入一个整数");
            } catch (AgeOutOfBoundsException e) {
                System.out.println(e.toString());
                System.out.println("请输入一个符合范围的年龄");
            }
        }
        System.out.println(s);
    }
}

在这个练习中:

  • 校验数据 时使用 throw 抛出异常,阻止不合法的赋值。
  • 主调方法 中用 try-catch 自行处理,让程序可以重复输入,而不是直接终止。

自定义异常

当 Java 内置的异常类不能清晰表达业务含义时,就需要自定义异常

自定义异常步骤:

  1. 定义类,类名要见名知意(如 AgeOutOfBoundsException)。
  2. 继承 RuntimeException(运行时)或 Exception(编译时),多数情况下继承 RuntimeException
  3. 提供空参构造和带消息的构造方法。

项目结构示例:

src/
└── com/wb/exce/
    ├── AgeOutOfBoundsException.java   // 自定义异常 
    ├── Student.java                   // 学生实体类 
    └── ExceptionDemo12.java           // 测试入口 
// 自定义运行时期异常 
public class AgeOutOfBoundsException extends RuntimeException {
    public AgeOutOfBoundsException() {
    }
 
    public AgeOutOfBoundsException(String message) {
        super(message);
    }
}

StudentsetAge 方法中使用:

public void setAge(int age) {
    if (age >= 18 && age <= 25) {
        this.age = age;
    } else {
        throw new AgeOutOfBoundsException("年龄超出了范围");
    }
}

当调用者捕获到 AgeOutOfBoundsException 时,就能精确知道是年龄超出范围,而不再是一个模糊的 RuntimeException,达到了异常信息见名知意的目的。

总结

以上便是 Java 异常处理机制的基础入门,从异常体系、JVM 默认处理,到两种处理方式(throws/throwtry-catch),再到自定义异常的编写,构成了一个完整的知识闭环

动手实践这些示例,能够帮助快速掌握异常处理的核心理念

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值