首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【JavaSE】异常 && throw && throws && try-catch && 自定义异常类 && 多线程异常情况

【JavaSE】异常 && throw && throws && try-catch && 自定义异常类 && 多线程异常情况

原创
作者头像
lirendada
发布2026-03-20 14:02:40
发布2026-03-20 14:02:40
720
举报
文章被收录于专栏:JavaJava

Ⅰ. 什么是异常

Java 中,将程序执行过程中发生的不正常行为称为异常。

比如算数异常 ArithmeticException、数组越界异常 ArrayIndexOutOfBoundsException、空指针异常 NullPointerException 等等。

从上图中可以看到:

  1. Throwable是异常体系的顶层类,其派生出两个重要的子类,ErrorException
  2. ErrorJava 虚拟机无法解决的严重问题,比如:JVM 的内部错误、资源耗尽等,典型代表:StackOverflowErrorOutOfMemoryError,一旦发生回力乏术。 这种异常不需要去捕获
  3. Exception异常产生后程序员可以通过代码进行处理,使程序继续执行。我们平时所说的异常就是这个类。

异常可分为:

  • 编译时异常:在程序编译期间发生的异常,也称为受检查异常
  • 运行时异常:在程序执行期间发生的异常,也称为非受检查异常RunTimeException 以及其子类对应的异常,都属于运行时异常

注意:编译时出现的语法错误,不能称之为异常

Ⅱ. 事后认错型异常

EAFPIt's Easier to Ask Forgiveness than Permission)即先操作,遇到问题再处理

代码语言:javascript
复制
try { 
    登陆游戏(); 
    开始匹配(); 
    游戏确认(); 
    选择英雄(); 
    载入游戏画面(); 
    ... 
} catch (登陆游戏异常) { 
    处理登陆游戏异常; 
} catch (开始匹配异常) { 
    处理开始匹配异常; 
} catch (游戏确认异常) { 
    处理游戏确认异常; 
} catch (选择英雄异常) { 
    处理选择英雄异常; 
} catch (载入游戏画面异常) { 
    处理载入游戏画面异常; 
} 
......

优势:正常流程和错误流程是分离开的,程序员更关注正常流程,代码更清晰,容易理解代码。

一、异常抛出 throw

使用 throw 关键字可以抛出一个指定的异常对象,将错误信息告知调用者。

代码语言:javascript
复制
public static int func(int n) {
    if(n == 0) {
        throw new ArithmeticException("除0异常"); // 除零则抛出异常
    }
    return 1/n;
}

public static void main(String[] args) {
    try {
        System.out.println(func(0));
    } catch (ArithmeticException e) {
        e.printStackTrace();
    }
}

// 结果
java.lang.ArithmeticException: 除0异常
at abnormal.demo2.func(demo2.java:3)
at abnormal.demo2.main(demo2.java:10)

注意事项

  1. throw 必须写在方法体内部
  2. 抛出的对象必须是 Exception 或者 Exception的子类对象
  3. 如果抛出的是 RunTimeException 或者 RunTimeException的子类,则可以直接交给 JVM 处理
  4. 如果抛出的是编译时异常(受查异常),用户必须处理,否则无法通过编译
  5. 异常一旦抛出,其所在代码块后面的代码不会执行

二、异常声明 throws

当方法中抛出编译时异常(受查异常),并且用户不想处理该异常,此时就可以借助 throws 将异常抛给方法的调用者来处理。

当前方法不处理异常,只是提醒方法的调用者处理异常

代码语言:javascript
复制
修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2...{ 
}

注意事项

  1. throws 必须跟在方法的参数列表之后
  2. 声明的异常必须是 Exception 或者 Exception子类
  3. 方法内部如果抛出了多个异常,throws 之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型具有父子关系,直接声明父类即可
  4. 调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用 throws 抛出给上一层调用者。如果一直得不到异常处理,则程序会直接终止

三、try-catch 捕获并处理

throws 对异常并没有真正处理,而是将异常报告给抛出异常方法的调用者,由调用者处理。

如果真正要对异常进行处理,就需要 try-catch

代码语言:javascript
复制
try{ 
    // 将可能出现异常的代码放在这里 
} catch(要捕获的异常类型 e) { 
    // 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的基类时,就会被捕获到 
    // 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后序代码 
    System.out.println(e.getMessage()); // 只打印异常信息 
    System.out.println(e);              // 打印异常类型:异常信息 
    e.printStackTrace();                // 打印信息最全面
} catch(异常类型 e){ 
    // 对异常进行处理 
} finally{ 
    // 此处代码一定会被执行到,常用于释放变量
}

// 后序代码: 
//    1. 当异常被捕获到时,异常就被处理了,这里的后序代码一定会执行 
//    2. 如果捕获了,由于捕获时类型不对,那就没有捕获到,这里的代码就不会被执行

注意事项

  1. try 块内抛出异常位置之后的代码将不会被执行,和 throw 的注意事项是一样的!
  2. 如果抛出异常类型与 catch 时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到 JVM 收到后中断程序,即异常是按照类型来捕获的
  3. 如果多个异常的处理方式是完全相同,可以用 | 号合并多个异常,如下所示:
    代码语言:javascript
    复制
    catch (ArrayIndexOutOfBoundsException | NullPointerException e) { 
        ... 
    }
  4. 如果异常之间具有父子关系,一定是子类异常在前 catch,父类异常在后 catch,否则语法错误。
  5. finally 执行的时机是在方法返回的时候,但是如果 finally 中存在 return 语句,try-catch 中也存在 return 语句,那么就会执行的是 finally 中的 return,而 try-catch 中原有的 return 最终被覆盖了。
  6. 所以一般不建议在 finally 中写 return 语句

Ⅲ. 异常处理流程

方法之间是存在相互调用关系的,这种调用关系我们可以用 "调用栈" 来描述。在 JVM 中有一块内存空间称为 "虚拟机栈" 专门存储方法之间的调用关系。当代码中出现异常的时候,我们就可以使用 e.printStackTrace() 查看出现异常代码的调用栈。

注意:如果本方法中没有合适的处理异常的方式,异常就会沿着调用栈向上传递,直到被处理或者程序终止

代码语言:javascript
复制
public class demo3 {
    public static void func1() {
        throw new NullPointerException("空指针异常");
    }

    public static void func2() {
        try {
            func1();
        } catch (IndexOutOfBoundsException e) {
            System.out.println("1111111111111");
        }
    }

    public static void main(String[] args) {
        try {
            func2();
        } catch (NullPointerException e) {
            e.printStackTrace();  
        }
    }
}

// 运行结果
java.lang.NullPointerException: 空指针异常
        at abnormal.demo3.func1(demo3.java:3)
        at abnormal.demo3.func2(demo3.java:8)
        at abnormal.demo3.main(demo3.java:16)

异常处理流程总结

  1. 程序先执行 try 中的代码
  2. 如果 try 中的代码出现异常,就会结束 try 中的代码,看和 catch 中的异常类型是否匹配
  3. 如果找到匹配的异常类型,就会执行 catch 中的代码
  4. 如果没有找到匹配的异常类型,就会将异常向上传递到上层调用者
  5. 无论是否找到匹配的异常类型,finally 中的代码都会被执行到(在该方法结束之前执行)
  6. 如果上层调用者也没有处理的了异常,就继续向上传递
  7. 一直到 main 方法也没有合适的代码处理异常,就会交给 JVM 来进行处理,此时程序就会异常终止

Ⅳ. 自定义异常类

Java 中虽然已经内置了丰富的异常类,但是并不能完全表示实际开发中所遇到的一些异常,此时就需要维护符合我 们实际情况的异常结构。

只需要实现一个自定义类,然后继承 Exception 或者 RuntimeException 即可

【注意事项】

  • 自定义异常通常会继承自 Exception 或者 RuntimeException
    • 继承自 Exception 的异常默认是 受查异常
    • 继承自 RuntimeException 的异常默认是 非受查异常

例如,我们实现一个用户登陆功能。

代码语言:javascript
复制
// 用户名异常类,继承于Exception 
class NameException extends Exception {
    public NameException(String message) {
        super(message);
    }
}

// 密码异常类,继承于Exception 
class PasswordException extends Exception {
    public PasswordException(String message) {
        super(message);
    }
}

public class demo {
    private String name = "liren";
    private String password = "123456";

    public void login() throws NameException, PasswordException {
        if (!name.equals("liren")) {
            throw new NameException("用户名错误");
        }
        if (!password.equals("654321")) {
            throw new PasswordException("密码错误");
        }
        System.out.println("登录成功");
    }

    public static void main(String[] args) {
        try {
            demo4 f = new demo4();
            f.login();
        } catch (NameException e) {
            e.printStackTrace();
        } catch (PasswordException e) {
            e.printStackTrace();
        }
    }
}

Ⅴ. 多线程异常情况

💥 一个线程抛出了异常,此时有以下不同情况,结论如下所示:

  1. 未捕获异常:默认情况下,仅终止该线程,不会影响其他线程或整个 JVM
  1. 捕获了异常:该线程可以继续运行,不会影响其他线程或整个 JVM
  2. 未捕获异常处理器:可以通过设置线程的未捕获异常处理器 UncaughtExceptionHandler,来处理未捕获的异常。如果未捕获异常处理器捕获了异常,线程会被终止,但不会影响其他线程或整个 JVM
  3. 线程组的未捕获异常处理器:如果线程没有设置自己的未捕获异常处理器,JVM 会使用线程所属线程组的默认未捕获异常处理器。默认行为是打印异常堆栈信息并终止该线程,不会影响其他线程或整个 JVM
  4. 线程抛出 Error:这种异常就可能导致整个 JVM 终止,因为 Error 表示严重的系统问题,比如说内存溢出等情况。

也就是说,只有最后一种情况,线程抛出了 Error,才有可能导致整个 JVM 的终止

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Ⅰ. 什么是异常
  • Ⅱ. 事后认错型异常
    • 一、异常抛出 throw
    • 二、异常声明 throws
    • 三、try-catch 捕获并处理
  • Ⅲ. 异常处理流程
  • Ⅳ. 自定义异常类
  • Ⅴ. 多线程异常情况
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档