Java教程

【Java后台开发规范】--- 异常的处理

本文主要是介绍【Java后台开发规范】--- 异常的处理,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

文章目录

  • 前言
    • 其他类型的规范
  • 异常
    • 异常架构
      • Throwable
      • Error
      • Exception
        • 运行时异常
        • 编译时异常
    • 异常使用的误区
      • 忽视异常
      • 标准化异常
      • 正确的使用异常
      • 让异常保持原子性
        • 让异常前置
        • 不可变对象
        • 临时拷贝
        • 补偿
    • 其他补充

前言

做Java开发的,大多数可能都有看过阿里的Java后台开发手册,里面有关于Java后台开发规范的一些内容,基本覆盖了一些通用、普适的规范,但大多数都讲的比较简洁,本文主要会用更多的案例来对一些规范进行解释,以及结合自己的经验做补充!

其他类型的规范

【Java后台开发规范】— 不简单的命名
【Java后台开发规范】— 日志的输出
【Java后台开发规范】— 线程与并发
【Java后台开发规范】— 长函数、长参数
【Java后台开发规范】— 设计原则
【Java后台开发规范】— 圈复杂度
【Java后台开发规范】— Null值处理

异常

异常处理应该算是一种我们非常熟悉的话题了,Java中对于异常的处理也非常便捷、灵活,但往往越是简单的东西,越容易忽视它,恰巧异常也存在很多容易忽视的陷阱,一起来看看吧!

异常架构

在这里插入图片描述

Throwable

Throwable是所有异常的错误的父类,printStackTrace()方法就是由Throwable提供的。

Error

Error表示程序遇到了无法处理的问题,出现了严重的错误,常见的比如:OutOfMemoryError,StackOverflowError

Exception

程序本身可以处理的异常,Exception类本身又分为两类:运行时异常和编译时异常。

运行时异常

RuntimeException类及其子类产生的异常,编译时不会进行检查,只有在程序运行时才会产生,也可以通过try-catch来进行处理,但通常不需要我们这样做,因为运行时异常一般都是我们代码本身编写存在问题,应该在处理逻辑上进行修正。

常见的有:NullPointerException,ArrayIndexOutBoundException,ClassCastException

编译时异常

Exception下除了RuntimeException类型的其他异常都是编译时异常,这类异常在编译时就会进行检查,并强制要求对其进行处理,否则无法通过编译。

常见的有:ClassNotFoundException、IOException

异常使用的误区

忽视异常

绝大多数情况下都不应该像如下这样忽视异常的存在,因为这样会让你无法发现问题。

try{
	doSomething();
}catch(Exception e){
    // 什么也不做
}

当然也有例外

如果选择了忽略异常,那么最好在catch中通过注释的方式给出原因,并且变量名使用ignored

下面两个案例,都将变量名改为了ignored,但都没有在catch中给出具体的原因。

在这里插入图片描述

原因写在了方法注释上。

在这里插入图片描述

这个解释绝绝子,不可能发生

在这里插入图片描述

标准化异常

统一语言、统一认知一直是我们强调的,让异常标准化也算其实现手段之一,得益于标准化好处,当你看到如下这些异常时,会感到非常的熟悉:NullPointerException、IllegalArgumentException、IllegalStateException、ClassCastException、IllegalFormatConversionException、IndexOutOfBoundsException

如果没有这些标准化的异常分类,实际上所有的异常都可以归为IllegalStateException(非法状态)或者IllegalArgumentException(非法参数)。

TreeMap中的Key不允许为null

在这里插入图片描述

HashTable中的value不允许为null

在这里插入图片描述

以上两个案例,实际上都可以按照IllegalArgumentException(非法参数)来处理,但是作者并没有这样做,IndexOutOfBoundsException异常也一样,并没有用IllegalArgumentException来替代。

常见的一些标准异常:

IllegalArgumentException

IndexOutOfBoundsException

NullPointerException

ClassCastException

IllegalFormatConversionException

UnsupportedOperationException

正确的使用异常

一种基于异常的循环控制,这种做法的原因是因为有人认为JVM底层就是这样终止的。

List<User> userList = new ArrayList<>();
userList.add(new User("a"));
userList.add(new User("b"));
userList.add(new User("c"));
userList.add(new User("d"));

try {
    int i = 0;
    while (true) {
        User user = userList.get(i++);
        System.out.println(user.getName());
    }
} catch (IndexOutOfBoundsException e) {
    // 什么也不做
}

比如,正常你应该会写成像下面这样,那JVM又是怎么判断数据边界的呢?

for (User user : userList) {
    System.out.println(user.getName());
}

为了省去每次的边界检查,所以采用异常捕获的方式,这明显是错误的,实际上测试对比后,后者比前者快很多,原因主要在于以下两点:

  • 写在try-catch中的代码,JVM一般不会对其进行优化。
  • 而数组的遍历,经过JVM优化后不会造成多余的边界检查。

基于上述这个案例,也告诫我们在做设计时,不要企图让你的调用者通过异常控制的方式来完成正常的流程。

再来看一个案例

Iterator<User> iterator = userList.iterator();
while(iterator.hasNext()){
    User user = iterator.next();
}

假如Iterator没有提供hasNext方法,那可能你只能通过try-catch的方式来解决了。

Iterator<User> iterator = userList.iterator();
try{
    while(true){
        User user = iterator.next();
    }
} catch(NoSuchElementException e){

}

让异常保持原子性

这条原则的含义是指,当调用某行代码产生异常时,应该使当前对象仍能保持异常前的数据状态。

通常有下面几种方式:

让异常前置

举一个list集合移除元素的例子,其中rangeCheck方法中对当前集合的size做了检查,如果index>=size则抛出异常

public E remove(int index) {
    rangeCheck(index);
    modCount++;
    E oldValue = elementData(index);
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
    return oldValue;
}

private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

其实通过这个简单的rangeCheck方法就能让异常保持原子性,因为它使得在modCount在修改之前就已经抛出了异常,假设你没有提前做rangeCheck检查,那么你在调用E oldValue = elementData(index)这一行时,仍然会遇到IndexOutOfBoundsException异常,但modCount状态却已经被修改了,你不得不再去维护它的状态。

不可变对象

很多场景中不可变对象总是安全的,异常也不例外。

临时拷贝

如果你每次操作的都是新拷贝出来的对象,那么即使失败了,也并没有对原数据产生影响。

补偿

通过手动补偿的方式来保证失败后状态的正确性,就有点像如何解决分布式事务的问题,在遇到失败后,主动调用一段事先准备好的回滚逻辑,使数据回到失败前的状态。

其他补充

  • 处于事务中的流程,如果catch了异常,要注意事务的回滚。
  • 尽量避免在循环体中try-catch异常。
  • 不要用异常来控制流程
  • 使用try-with-resources替代try-catch-finally
这篇关于【Java后台开发规范】--- 异常的处理的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!