资讯专栏INFORMATION COLUMN

Java 异常实战

libxd / 3274人阅读

摘要:不过按照经验来说,这类异常要么尽量避免,要么出现了就要做异常处理,从而保证程序的健壮性。业务是千变万化,但是它们可能产生的异常处理方式是不会变化的,按照这个思路去做异常处理即可。

前言:说到异常体系,可能对于一些初入职场的老铁会很头痛,不能够很清晰的描述异常是个什么情况。那么本文将通过打流水仗的方式给大家介绍一下工作中涉及的异常知识。首先能看到本文,说明也对异常是有了解的,所以文章开头就通过一些概念和小例子快速熟悉一下异常,紧接着介绍异常体系,对于理解整个异常体系架构非常有帮助,最后介绍了非常非常重要的自定义异常,学会了这些,在工作中遇到的常见异常问题,处理起来一定都会游刃有余啦。
异常概述 异常

异常就是在程序的运行过程中所发生的不正常的事件,它会中断正在运行的程序

异常处理

Java 编程语言使用异常处理机制为程序提供了错误处理的能力,好处就是:如果代码中存在了异常,但是进行了捕获处理,那么程序就会继续运行下去,不会因为一个异常导致程序中断运行。

例子1:如果程序可能存在异常但是没有做异常处理,那么将会导致程序不能正常的运行下去:

public class ExceptionTest {
    public static void main(String[] args) {
        int a = 10;
        int b = 0;
        int c = a / b;
        System.out.println(c);
        System.out.println("输出了本句话吗");
    }
}

输出结果是:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at cn.caijiajia.cn.caijiajia.exception.ExceptionTest.main(ExceptionTest.java:12)

例子2:如果程序可能存在异常并做了异常处理,那么程序就会正常的运行下去:

public class ExceptionTest {
    public static void main(String[] args) {
        try {
            int a = 10;
            int b = 0;
            int c = a / b;
            System.out.println(c);
            System.out.println("异常代码下面的代码将都不会执行");
        } catch (ArithmeticException e) {
            System.out.println("处理异常的代码");
        }
        System.out.println("异常处理代码下面的程序将会继续执行而不会程序中断");
    }
}

输出结果是:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at cn.caijiajia.cn.caijiajia.exception.ExceptionTest.main(ExceptionTest.java:12)
处理异常的代码
异常处理代码下面的程序将会继续执行而不会程序中断
    
Java 中如何处理异常 异常处理的 5 个关键字

try:执行可能产生异常的代码
catch:捕获异常并对异常情况做相应处理
finally:无论是否发生异常,代码总能执行。(释放资源,关闭数据库连接)
throws:声明可能抛出的各种异常(受检异常较多)
throw:手动的抛出异常(手动抛出我们自定义的异常较多)

异常处理后程序运行情况

情况一:没产生异常

public void method() {
    try {
        // 代码段① [正常业务逻辑代码,此处不会产生异常]
    } catch (Exception e) {
        // 代码段② [对异常处理的代码段]
    }
    // 代码段③ [正常业务逻辑代码]
}

运行结果:
    代码段①
    代码段③

情况二:产生异常并捕获异常

public void method() {
    try {
        // 异常代码段① [正常业务逻辑代码,此处会产生异常]
        // 代码段② [正常业务逻辑代码]
    } catch (Exception e) {
        // 代码段③ [对异常处理的代码段]
    }
    // 代码段④ [正常业务逻辑代码]
}

运行结果:
    代码段①
    代码段③   
    代码段④

注意:如果想要正常的 代码段② 执行,那么可以把代码段从 trycatch 里面提出来,和 代码段④ 放在一起,当异常处理完之后,就可以同时去执行 代码段② 和 代码段④ 了。
    

情况三:产生异常并捕获异常,但是捕获异常类型不匹配产生异常类型

public void method() {
    try {
        // 异常代码段① [正常业务逻辑代码,此处会产生角标越界异常]
        // 代码段② [正常业务逻辑代码]
    } catch (IOException e) {
        // 代码段③ [对异常处理的代码段]
    }
    // 代码段④ [正常业务逻辑代码]
}

运行结果:
    代码段①
    
注意:如果捕获异常类型和产生的异常类型不匹配,那么就和没有处理异常情况一样了,trycatch 后面的代码段将都不会执行,发生异常就会导致程序中断运行
异常体系 异常类层次图

Error 和 Exception

Error 类和 Exception 类的父类都是 Throwable 类

Error 类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。因此我们在学习的时候主要是学会 Exception。

Exception 类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。

Exception 异常分类及处理

Exception 异常主要分为两大类:Checked Exception,Unchecked Exception;即受检异常和非受检异常(运行时异常)

Checked Exception:受检异常,即 Java 程序必须显式处理的异常,如果程序没有处理 Checked 异常,该程序在编译时就会发生错误无法编译。例如图中所示的 IOException

处理受检异常通过有如下两种方式:
方式一:通过 trycatch 显式处理异常,否则编译不通过

public class ExceptionTest {
    public static void main(String[] args) {
        try {
            // 通过 trycatch 显示处理异常
            FileInputStream fis = new FileInputStream(new File(""));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

方式二:通过 throws 抛出异常,让上层来处理异常,否则编译不通过

public class ExceptionTest {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream(new File(""));
    }
}

Unchecked Exception:非受检异常,即 RuntimeException 运行时异常。这些异常程序可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。比如常见的 NullPointerException、IndexOutOfBoundsException 就是运行时异常。不过按照经验来说,这类异常要么尽量避免,要么出现了就要做异常处理,从而保证程序的健壮性。

处理运行时异常通过有如下两种方式:
方式一:常见的空指针处理

// 通过写伪代码来演示其处理流程
if (对象 == null) {
    // 处理对象为 null 的逻辑
} else {
    // 处理对象不为 null 的逻辑
}

方式二:跟业务相关异常,抛出自定义异常

// 通过手机号注册业务逻辑
User user = dao.getByPhone(phone);
if (user != null) {
    // 说明此手机号码已经被注册,那么就抛出业务异常(即自定义异常)
    throw new MyRuntimeException("改用户已注册");
}
// 如果没有注册,继续走注册流程代码

总结:通过本小节异常分类及处理可以发现,受检异常处理起来很简单,因为程序如果不做显式处理,那么就会编译不通过,强制要求处理;而运行时异常则是看心情处理的,但是如果想要公司代码更加健壮,更少的出现问题,最好要做一下异常处理;但是如果做这个处理呢?对于这种运行时异常,大部分都是和业务相关的,比如手机号注册例子;这种情况下在 Java 的异常体系中并没有相关异常类做处理,因为 Java 不管在智能,也不可能知道我们的业务情况,当然就不会针对业务提供一些异常类供我们使用,因此为了解决这个问题,自定义异常就出现了,它对于我们处理业务中产生的运行时异常非常非常重要,接下来就来学习自定义异常。

自定义异常 产生原因

Java 现有的异常类不能满足更多的业务异常处理,因此我们要自定义合适的异常类来处理业务异常。

如何自定义异常类

第一步:声明一个类继承 Exception 或其子类

那么我们声明的这个类到底继承谁呢?Exception?RuntimeException?
答案是:RuntimeException
原因是:看了上面的异常类层次图,应该也能发现,Exception 下面有两大类子类,受检异常和运行时异常,如果我们自定义的类继承了 Exception,则就会因为受检异常的存在而变成了受检异常类,这个时候我们自定义的异常类,如果在程序中使用,那么就必须显式处理异常,要么 trycatch,要么抛出给上层;这样一来,使得我们的程序很混乱,而且并没有达到我们预期的结果。然后当我们自定义的类继承了 RuntimeException 之后,当我们程序中想要使用的时候,直接 new 一个即可,而不再需要显式去再多做处理了。

第二步:自定义异常类应至少包含四个构造方法

public class MyException extends RuntimeException {
    public MyException() {}

    public MyException(String msg) {
        super(msg);
    }

    public MyException(Throwable throwable) {
        super(throwable);
    }

    public MyException(String msg, Throwable throwable) {
        super(msg, throwable);
    }
}

第三步:在程序中使用我们的自定义异常

public void testException() {
    User user = dao.getByPhone(phone);
    if (user != null) {
        // 因为 MyException 是继承 RuntimeException,所以这里直接抛出异常而不用做其它处理
        throw new MyException("该手机号已被注册");
    }
    // ...
} 

注:没错,这里又使用了这个手机号注册的例子,因为这个就是实实在在的在业务中的异常处理。业务是千变万化,但是它们可能产生的异常处理方式是不会变化的,按照这个思路去做异常处理即可。

tips:如上自定义的异常类中的构造方法是最基本的几个。每家公司的自定义异常可能都会有区别,比如定义一个 SuperException 类实现了 RuntimeException,然后在自定义 ClientException,ServerException 再去继承 SuperException;那么这就是一套自定义体系了,分为客户端异常和服务端异常,在需要做异常处理的地方使用对应的异常类并抛出异常错误信息即可了。再比如可能有的公司会在自定义异常类中在定义一些字段,code,msg 等,来代表某些业务码和对应的错误信息等。不管怎么样,我们只要了解自定义异常的原理后,面对哪个公司的自定义异常体系我们都能够轻松应对。

异常总结

通过本文的学习,一定要掌握两个知识,一个是异常体系,要分清受检异常和运行时异常;另一个就是自定义异常,知道如何自定义异常和如何使用自定义异常。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68165.html

相关文章

  • Spring Boot 2.x 系列教程:WebFlux REST API 全局异常处理 Error

    摘要:挺多人咨询的,异常处理用切面注解去实现去全局异常处理。全局异常处理类,代码如下代码解析如下抽象类是用来处理全局错误时进行扩展和实现注解标记的切面排序,值越小拥有越高的优先级,这里设置优先级偏高。 本文内容 为什么要全局异常处理? WebFlux REST 全局异常处理实战 小结 摘录:只有不断培养好习惯,同时不断打破坏习惯,我们的行为举止才能够自始至终都是正确的。 一、为什么要全局...

    BicycleWarrior 评论0 收藏0
  • SpringBoot 实战 (十四) | 统一处理异常

    摘要:前言如题,今天介绍是如何统一处理全局异常的。主要是用于异常拦截出获取并将设置到消息类中返回。状态码异常拦截类通过加入来声明该类可拦截请求,同时在方法加入并在该注解中指定要拦截的异常类。测试访问测试正常返回数据结果。 微信公众号:一个优秀的废人如有问题或建议,请后台留言,我会尽力解决你的问题。 前言 如题,今天介绍 SpringBoot 是如何统一处理全局异常的。SpringBoot 中...

    arashicage 评论0 收藏0
  • java 8 实战》读书笔记 -第三章 Lambda表达式

    摘要:利用前面所述的方法,这个例子可以用方法引用改写成下面的样子构造函数引用对于一个现有构造函数,你可以利用它的名称和关键字来创建它的一个引用。 第三章 Lambda表达式 函数式接口 函数式接口就是只定义一个抽象方法的接口,哪怕有很多默认方法,只要接口只定义了一个抽象方法,它就仍然是一个函数式接口。 常用函数式接口 showImg(https://segmentfault.com/img...

    whinc 评论0 收藏0
  • java 8 实战》读书笔记 -第十三章 函数式的思考

    摘要:当我们希望能界定这二者之间的区别时,我们将第一种称为纯粹的函数式编程,后者称为函数式编程。函数式编程我们的准则是,被称为函数式的函数或方法都只能修改本地变量。另一种观点支持引用透明的函数式编程,认为方法不应该有对外部可见的对象修改。 一、实现和维护系统 1.共享的可变数据 如果一个方法既不修改它内嵌类的状态,也不修改其他对象的状态,使用return返回所有的计算结果,那么我们称其为纯粹...

    Donne 评论0 收藏0
  • SpringBoot 实战 (十) | 声明式事务

    摘要:前言如题,今天介绍的声明式事务。提供一个注解在配置类上来开启声明式事务的支持。而在配置里还开启了对声明式事务的支持,代码如下所以在中,无须显式开启使用注解。源码下载后语以上为声明式事务的教程。 微信公众号:一个优秀的废人如有问题或建议,请后台留言,我会尽力解决你的问题。 前言 如题,今天介绍 SpringBoot 的 声明式事务。 Spring 的事务机制 所有的数据访问技术都有事务处...

    ygyooo 评论0 收藏0

发表评论

0条评论

libxd

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<