你尽管try我尽量catch,二当家的带你搞明白java的异常机制

简介: java的异常机制你都懂了么?二当家的一文带你深入浅出,理解透彻。

前言

java的异常机制你都懂了么?二当家的一文带你深入浅出,理解透彻。


程序异常

异常本质上是程序上的错误,错误在我们编写程序的过程中会经常发生,包括编译期间和运行期间的错误。

  1. 编译期间的错误:

    通常都是语法错误,这个不是重点,毕竟运行不了的程序不会发生什么大事。

  2. 运行期间的错误:

    有的错误程序没办法管,有的错误程序不应该管,还有的错误程序必须管。运行时的错误如果没有处理好,业务上是会出问题的哦,很可能是一发而不可收拾呢。


运行期间的错误怎么管

发生错误,我们首先得知道,怎么知道呢?java里用try-catch语句处理运行期间的错误,用关键字try去执行可能发生错误的代码,然后用关键字catch去捕获程序运行期间发生的错误。

public class Test {

    private static void printDiv(int dividend, int divisor) {
        System.out.println(dividend + " / " + divisor + " = " + (dividend / divisor));
    }

    public static void main(String[] args) {
        printDiv(10, 5);
        printDiv(10, 0);
        printDiv(10, 2);
    }
}

在这里插入图片描述
上面的代码分别输出 10 / 5,10 / 0 和 10 / 2 的结果。但是运行结果显示,在执行 10 / 0 时程序就异常退出了,0是不能作为除数的。

接下来我们修改下代码,让我们的程序可以兼容异常情况。

public class Test {

    private static void printDiv(int dividend, int divisor) {
        try {
            System.out.println(dividend + " / " + divisor + " = " + (dividend / divisor));
        } catch(ArithmeticException e) {
            System.out.println(dividend + " / " + divisor + " 发生错误:" + e.getMessage());
        }
    }

    public static void main(String[] args) {
        printDiv(10, 5);
        printDiv(10, 0);
        printDiv(10, 2);
    }
}

在这里插入图片描述
在使用try-catch语法处理了错误以后,可以看到,当程序执行到发生错误的代码行,就会跳转到catch的块里继续执行,程序没有再异常退出了。


catch可以捕获哪些错误

cache捕获的都是Throwable类和它的子类,Throwable下有两个大类ErrorException。其他的错误或异常都应该继承自他们俩。
在这里插入图片描述

  1. Error

    Error是系统级别的错误,不该由应用程序捕获处理,应该交给系统或者框架来捕获处理。

  2. Exception

    Exception是程序级别的错误,应该由程序模块去捕获处理。


如何发起一个异常

我们自己写的代码也会有异常情况,如何发起一个异常情况呢?可以使用throw关键字。

public class Test {
    private static void someMethod() {
        // 使用throw关键字抛出异常
        throw new RuntimeException("运行中发生异常");
    }

    public static void main(String[] args) {
        someMethod();
        System.out.println("主方法执行完毕");
    }
}

在这里插入图片描述
我们也可以抛出Error和它的子类,但是通常我们应该抛出Exception和它的子类。之前已经讲过他们的区别。

public class Test {
    private static void someMethod() {
        // 使用throw关键字抛出错误
        throw new Error("运行中发生错误");
    }

    public static void main(String[] args) {
        someMethod();
        System.out.println("主方法执行完毕");
    }
}

在这里插入图片描述

我们抛出另外一种异常,竟然编译就不通过了。

import java.sql.SQLException;

public class Test {
    private static void someMethod() {
        // 使用throw关键字抛出异常
        throw new SQLException("运行中发生异常");
    }

    public static void main(String[] args) {
        someMethod();
        System.out.println("主方法执行完毕");
    }
}

在这里插入图片描述


可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)

在这里插入图片描述

  1. 可查的异常(checked exceptions)

    除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。Java编译器会检查这种异常,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。

  2. 不可查的异常(unchecked exceptions)

    不可查异常(编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)和错误(Error)。

原来SQLException属于可查的异常(checked exceptions),我们必须用try-catch捕获,或者声明抛出,捕获我们已经会了,怎么声明抛出呢?可以使用throws关键字。

import java.sql.SQLException;

public class Test {
    private static void someMethod() throws SQLException {
        // 使用throw关键字抛出异常
        throw new SQLException("运行中发生异常");
    }

    public static void main(String[] args) throws SQLException {
        someMethod();
        System.out.println("主方法执行完毕");
    }
}

这样就可以编译通过了呢。


自定义异常

java已经内置了很多错误和异常类型,我就不罗列了,很容易查到,当内置的异常类型不能正确表达我们的错误时,我们也可以自定义错误和异常类型,只需要继承Error或者Exception

public class Test {
    /**
     * 自定义的异常类型
     */
    static class MyRuntimeException extends RuntimeException {
        public MyRuntimeException(String message) {
            super(message);
        }
    }

    private static void someMethod() {
        // 使用throw关键字抛出异常
        throw new MyRuntimeException("运行中发生异常");
    }

    public static void main(String[] args) {
        someMethod();
        System.out.println("主方法执行完毕");
    }
}

try的姿势

try的一般姿势有三种

public class Test {
    public static void main(String[] args) {
        // 姿势一
        try {
            // 有可能抛出异常的部分
        } catch (Exception e) {
            // 对异常的处理
        }

        // 姿势二
        try {
            // 有可能抛出异常的部分
        } finally {
            // 不管是否发生异常,都要执行的部分
        }

        // 姿势三
        try {
            // 有可能抛出异常的部分
        } catch (Exception e) {
            // 对异常的处理
        } finally {
            // 不管是否发生异常,都要执行的部分
        }
    }
}

上面的方式都是针对错误进行统一的处理,如果一段代码可能发生多种错误,我要针对某种错误进行特殊的处理怎么办?

import java.io.IOException;

public class Test {

    /**
     * 将结果写入文件
     * @param ret
     * @throws IOException
     */
    private static void writeFile(int ret) throws IOException {
        // 我假装将结果写入文件,其实没那么干,但是那是我的事,调用的人请保密
        throw new IOException("磁盘满了啦");
    }

    private static void printDiv(int dividend, int divisor) {
        try {
            int ret = dividend / divisor;
            System.out.println(dividend + " / " + divisor + " = " + ret);
            writeFile(ret);
        } catch(Exception e) {
            if (e instanceof IOException) {
                System.out.println(dividend + " / " + divisor + " 发生IO错误:" + e.getMessage());
            } else {
                System.out.println(dividend + " / " + divisor + " 发生错误:" + e.getMessage());
            }
        }
    }

    public static void main(String[] args) {
        printDiv(10, 5);
        printDiv(10, 0);
        printDiv(10, 2);
    }
}

在这里插入图片描述
上面的代码达到了想要的效果,然而有点傻,其实java已经有语法直接处理这种情况。下面才是正确的方式哦,执行结果和上面的一样。

import java.io.IOException;

public class Test {

    /**
     * 将结果写入文件
     * @param ret
     * @throws IOException
     */
    private static void writeFile(int ret) throws IOException {
        // 我假装将结果写入文件,其实没那么干,但是那是我的事,调用的人请保密
        throw new IOException("磁盘满了啦");
    }

    private static void printDiv(int dividend, int divisor) {
        try {
            int ret = dividend / divisor;
            System.out.println(dividend + " / " + divisor + " = " + ret);
            writeFile(ret);
        } catch (IOException e) {
            System.out.println(dividend + " / " + divisor + " 发生IO错误:" + e.getMessage());
        } catch(Exception e) {
            System.out.println(dividend + " / " + divisor + " 发生错误:" + e.getMessage());
        }
    }

    public static void main(String[] args) {
        printDiv(10, 5);
        printDiv(10, 0);
        printDiv(10, 2);
    }
}

有一点要注意,如果我们需要同时catch父子类异常,那么子类一定要先catch,因为java是按照代码顺序匹配的,如果把父类的catch块写在前面,那子类的catch块就永远不会运行到。

如果我要对发生的多种异常进行分组处理,其中几种用第一种处理方式,另外几种用第二种处理方式,其他的用第三种处理方式统一处理呢?我们当然可以cache多次,然后几个cache块写一样的内容,但是那样依然很傻。下面才是正确的方式。

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.WriteAbortedException;

public class Test {

    /**
     * 将结果写入文件
     * @param ret
     * @throws IOException
     */
    private static void writeFile(int ret) throws FileNotFoundException, WriteAbortedException {
        // 我假装将结果写入文件,其实没那么干,但是那是我的事,调用的人请保密
        int i = (int) (Math.random() * 3);

        switch (i) {
            case 0:
                throw new FileNotFoundException("文件不存在");
            case 1:
                throw new FileNotFoundException("文件写入被中断");
            case 2:
                throw new FileNotFoundException("磁盘满了啦");
        }
    }

    private static void printDiv(int dividend, int divisor) {
        try {
            int ret = dividend / divisor;
            System.out.println(dividend + " / " + divisor + " = " + ret);
            writeFile(ret);
        } catch (FileNotFoundException | WriteAbortedException e) {
            System.out.println(dividend + " / " + divisor + " 文件写入发生错误:" + e.getMessage());
        } catch (IOException e) {
            System.out.println(dividend + " / " + divisor + " 发生IO错误:" + e.getMessage());
        } catch(Exception e) {
            System.out.println(dividend + " / " + divisor + " 发生错误:" + e.getMessage());
        }
    }

    public static void main(String[] args) {
        printDiv(10, 5);
        printDiv(10, 0);
        printDiv(10, 2);
    }
}

在这里插入图片描述


什么情况需要用到finally块呢

先看下面一段代码

import java.io.FileWriter;
import java.io.IOException;

public class Test {

    /**
     * 写入文件
     * @param text
     * @throws IOException
     */
    private static boolean writeFile(String text) throws IOException {
        try {
            System.out.println("申请使用资源");
            FileWriter fw = new FileWriter("test.txt");
            // 在这之后可能发生异常
            fw.write(text);
            if (true) {
                throw new RuntimeException("运行中发生的异常");
            }
            // 这里关闭流资源,但是可能在这之前发生异常
            System.out.println("关闭资源");
            fw.close();
            return true;
        } catch (Exception e) {
            System.out.println("处理异常:" + e.getMessage());
            return false;
        }
    }

    public static void main(String[] args) throws IOException {
        writeFile("test");
    }
}

在这里插入图片描述

像IO流等系统资源,在从系统申请用完之后,需要手动释放,否则就会发生泄露。上面的代码,如果在关闭资源之前,发生了异常,程序就会跳到catch块继续执行,那关闭资源的代码就永远不会执行,直到程序退出。该怎么办呢?这时候就需要finally了。

import java.io.FileWriter;
import java.io.IOException;

public class Test {

    /**
     * 写入文件
     * @param text
     * @throws IOException
     */
    private static boolean writeFile(String text) throws IOException {
        FileWriter fw = null;
        try {
            System.out.println("申请使用资源");
            fw = new FileWriter("test.txt");
            // 在这之后可能发生异常
            fw.write(text);
            if (true) {
                throw new RuntimeException("运行中发生的异常");
            }
            return true;
        } catch (Exception e) {
            System.out.println("处理异常:" + e.getMessage());
            return false;
        } finally {
            if (fw != null) {
                System.out.println("关闭资源");
                fw.close();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        writeFile("test");
    }
}

在这里插入图片描述

上面的代码做了修改,将关闭资源的代码放到了finally块内,无论try是否发生异常,finally内的代码都会执行。不光是关闭资源,任何无论是否发生异常都应该执行的逻辑都可以放在finally块内。

另外注意一点,catch块也可能发生异常,但是finally块内的代码还是会执行。

import java.io.FileWriter;
import java.io.IOException;

public class Test {

    /**
     * 写入文件
     * @param text
     * @throws IOException
     */
    private static boolean writeFile(String text) throws IOException {
        FileWriter fw = null;
        try {
            System.out.println("申请使用资源");
            fw = new FileWriter("test.txt");
            // 在这之后可能发生异常
            fw.write(text);
            if (true) {
                throw new RuntimeException("运行中发生的异常");
            }
            return true;
        } catch (Exception e) {
            System.out.println("处理异常:" + e.getMessage());
            throw new RuntimeException("处理异常情况时发生异常");
//            return false;
        } finally {
            if (fw != null) {
                System.out.println("关闭资源");
                fw.close();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        writeFile("test");
    }
}

在这里插入图片描述
但是如果finally块发生异常,后面的代码就不会继续执行了。

import java.io.FileWriter;
import java.io.IOException;

public class Test {

    /**
     * 写入文件
     * @param text
     * @throws IOException
     */
    private static boolean writeFile(String text) throws IOException {
        FileWriter fw = null;
        try {
            System.out.println("申请使用资源");
            fw = new FileWriter("test.txt");
            // 在这之后可能发生异常
            fw.write(text);
            if (true) {
                throw new RuntimeException("运行中发生的异常");
            }
            return true;
        } catch (Exception e) {
            System.out.println("处理异常:" + e.getMessage());
            return false;
        } finally {
            if (true) {
                throw new RuntimeException("finally块发生异常");
            }
            if (fw != null) {
                System.out.println("关闭资源");
                fw.close();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        writeFile("test");
    }
}

在这里插入图片描述
finally块的代码通常是必须执行的,所以要catch掉可能的异常,try-catch语法是可以嵌套的哦。

import java.io.FileWriter;
import java.io.IOException;

public class Test {

    /**
     * 写入文件
     * @param text
     * @throws IOException
     */
    private static boolean writeFile(String text) throws IOException {
        FileWriter fw = null;
        try {
            System.out.println("申请使用资源");
            fw = new FileWriter("test.txt");
            // 在这之后可能发生异常
            fw.write(text);
            if (true) {
                throw new RuntimeException("运行中发生的异常");
            }
            return true;
        } catch (Exception e) {
            System.out.println("处理异常:" + e.getMessage());
            return false;
        } finally {
            try {
                throw new RuntimeException("finally块发生异常");
            } finally {
                if (fw != null) {
                    System.out.println("关闭资源");
                    fw.close();
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        writeFile("test");
    }
}

在这里插入图片描述


try-with-resources语法

像IO流这种有限系统资源的情况有很多,都大同小异,都是需要在最终使用完去手动调用关闭。每次都写模板一般的代码,有点傻,重复劳动,而且为了finally块和try块都可以访问那个变量,就必须声明在try-catch之外,这就导致finally块后面的代码还可以访问那个变量,这是不合理的。所以后来java有了新的语法来处理这种情况。实现AutoCloseable接口的类,都可以实现自动关闭。

import java.io.IOException;

public class Test {
    static class SomeResource implements AutoCloseable {
        public void use() {
            System.out.println("使用资源");
        }

        @Override
        public void close() {
            System.out.println("关闭资源");
        }
    }

    /**
     * 写入文件
     * @param text
     */
    private static boolean writeFile(String text) {
        try (SomeResource resource = new SomeResource();) {
            resource.use();
            // 在这之后可能发生异常
            if (true) {
                throw new RuntimeException("运行中发生的异常");
            }
            return true;
        }
    }

    public static void main(String[] args) throws IOException {
        writeFile("test");
    }
}

在这里插入图片描述

可以看到上面的代码使用资源后,并没有手动调用关闭方法,关闭方法是被自动调用的。我们之前使用的FileWriter已经实现了AutoCloseable接口,所以也可以自动关闭了。

可以在一个块里使用多个可自动关闭资源,顺序是初始化的逆序。

import java.io.IOException;

public class Test {
    static class SomeResource implements AutoCloseable {
        private final String name;

        SomeResource(String name) {
            this.name = name;
            System.out.println("初始化资源:" + name);
        }

        public void use() {
            System.out.println("使用资源:" + name);
        }

        @Override
        public void close() {
            System.out.println("关闭资源:" + name);
        }
    }

    /**
     * 写入文件
     *
     * @param text
     */
    private static boolean writeFile(String text) {
        try (
                SomeResource resource1 = new SomeResource("测试资源1");
                SomeResource resource2 = new SomeResource("测试资源2");
                SomeResource resource3 = new SomeResource("测试资源3");
        ) {
            resource2.use();
            resource1.use();
            resource3.use();
            // 在这之后可能发生异常
            if (true) {
                throw new RuntimeException("运行中发生的异常");
            }
            return true;
        }
    }

    public static void main(String[] args) throws IOException {
        writeFile("test");
    }
}

在这里插入图片描述


尾声

二当家的已经力求写的全面,如有不尽或者不准确的地方还请小伙伴们多见谅,欢迎评论区讨论。另外请赏个三连吧,多谢。


非常感谢你阅读本文~
放弃不难,但坚持一定很酷~
希望我们大家都能每天进步一点点~
本文由 二当家的白帽子:https://developer.aliyun.com/profile/sqd6avc7qgj7y 博客原创~
相关文章
|
25天前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
48 1
|
23天前
|
Java API 调度
如何避免 Java 中的 TimeoutException 异常
在Java中,`TimeoutException`通常发生在执行操作超过预设时间时。要避免此异常,可以优化代码逻辑,减少不必要的等待;合理设置超时时间,确保其足够完成正常操作;使用异步处理或线程池管理任务,提高程序响应性。
51 12
|
24天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
64 2
|
25天前
|
Java
在 Java 中,如何自定义`NumberFormatException`异常
在Java中,自定义`NumberFormatException`异常可以通过继承`IllegalArgumentException`类并重写其构造方法来实现。自定义异常类可以添加额外的错误信息或行为,以便更精确地处理特定的数字格式转换错误。
30 1
|
7天前
|
Java 程序员
深入理解Java异常处理机制
Java的异常处理是编程中的一块基石,它不仅保障了代码的健壮性,还提升了程序的可读性和可维护性。本文将深入浅出地探讨Java异常处理的核心概念、分类、处理策略以及最佳实践,旨在帮助读者建立正确的异常处理观念,提升编程效率和质量。
|
8天前
|
Java 开发者 UED
深入探索Java中的异常处理机制##
本文将带你深入了解Java语言中的异常处理机制,包括异常的分类、异常的捕获与处理、自定义异常的创建以及最佳实践。通过具体实例和代码演示,帮助你更好地理解和运用Java中的异常处理,提高程序的健壮性和可维护性。 ##
26 2
|
8天前
|
Java 开发者
Java中的异常处理机制深度剖析####
本文深入探讨了Java语言中异常处理的重要性、核心机制及其在实际编程中的应用策略,旨在帮助开发者更有效地编写健壮的代码。通过实例分析,揭示了try-catch-finally结构的最佳实践,以及如何利用自定义异常提升程序的可读性和维护性。此外,还简要介绍了Java 7引入的多异常捕获特性,为读者提供了一个全面而实用的异常处理指南。 ####
25 2
|
11天前
|
Java 程序员 UED
深入理解Java中的异常处理机制
本文旨在揭示Java异常处理的奥秘,从基础概念到高级应用,逐步引导读者掌握如何优雅地管理程序中的错误。我们将探讨异常类型、捕获流程,以及如何在代码中有效利用try-catch语句。通过实例分析,我们将展示异常处理在提升代码质量方面的关键作用。
24 3
|
11天前
|
Java 数据库连接 开发者
Java中的异常处理机制:深入解析与最佳实践####
本文旨在为Java开发者提供一份关于异常处理机制的全面指南,从基础概念到高级技巧,涵盖try-catch结构、自定义异常、异常链分析以及最佳实践策略。不同于传统的摘要概述,本文将以一个实际项目案例为线索,逐步揭示如何高效地管理运行时错误,提升代码的健壮性和可维护性。通过对比常见误区与优化方案,读者将获得编写更加健壮Java应用程序的实用知识。 --- ####
|
12天前
|
运维 Java 编译器
Java 异常处理:机制、策略与最佳实践
Java异常处理是确保程序稳定运行的关键。本文介绍Java异常处理的机制,包括异常类层次结构、try-catch-finally语句的使用,并探讨常见策略及最佳实践,帮助开发者有效管理错误和异常情况。
41 4