Java核心基础加强系列-java异常体系

相信大家在编程时都遇到过异常,出现异常是我们不想看到的,不过正是因为其会影响程序的正常运行,所以我们应当理解Java的异常体系,并知道怎样去优雅地处理异常,本文将聚焦这些内容。

Java的异常体系

先来展示一张大图: Java异常体系

想必Java程序员对这张图都非常熟悉,这张图展示了Java异常体系的继承关系。

Throwable类是整个Java异常体系的顶级父类,其有两个子类:Error和Exception。下面将对这两类异常一一进行介绍。

异常的分类

Error

Error表示系统致命错误,程序无法处理的错误,比如OutOfMemoryError、ThreadDeath等。这些错误发生时,Java虚拟机只能终止程序。这类异常我们没必要去捕获,就算捕获了也没有办法解决。

Exception

Exception是程序本身可以处理的异常,这种异常分两大类:运行时异常和非运行时异常。

1.运行时异常

运行时异常也叫做非必检(unchecked)异常,意思是我们在程序中可以对这类异常进行捕获,也可以不进行捕获,编译器不会报任何错,这类异常一般都是因为程序的逻辑错误导致的,最常见的可能就是NullPointerException和IndexOutOfBoundException等了,我们在书写程序的时候,要尽量避免这类异常的发生。

2.非运行时异常

除了RuntimeException及其子类的异常,其余的Exception均属于非运行时异常,也被称为必检(checked)异常,当我们调用的方法抛出了这类异常后,我们的代码必须捕获这类异常,否则编译器就会报错,程序不能编译通过。最常见的可能就是Java的IO体系中的IOException,FileNotFoundException等等了。

Java的异常处理

经典的try-catch-finally

下面来一段经典的代码:

/**
* User:        sunqingfeng6
* Date:        2018/10/31 16:02
* Description: 输入流测试类
*/
public class InputStreamTest {

   public static void main(String[] args) {
       InputStream inputStream = null;
       BufferedInputStream bufferedInputStream = null;
       try {
           inputStream = new FileInputStream("testIn.txt");
           bufferedInputStream = new BufferedInputStream(inputStream);
           byte[] buffer = new byte[1024];
           int length;
           while ((length = bufferedInputStream.read(buffer)) != -1) {
               System.out.println(new String(buffer, 0, length));
           }
       } catch (IOException ioException) {
           ioException.printStackTrace();
       } finally {
           if (null != inputStream) {
               try {
                   inputStream.close();
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }
           if (null != bufferedInputStream) {
               try {
                   bufferedInputStream.close();
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }
       }
   }
}

想必每个Java程序员都对这段代码非常熟悉,这是一段从文件中读取内容的典型代码,尽管真正读取文件内容的代码就那么几行,但是却花了大篇幅的代码在处理各种异常,并在finally中安全地关闭输入流资源,这样的代码显然不是我们想要的。

改进的try-with-resources

当然啦,作为一个追求代码艺术的程序员(手动不要脸╮(╯_╰)╭),我们是不会满足这样的代码的,所以很快就有了下面的这种简化的处理方式。

/**
 * User:        sunqingfeng6
 * Date:        2018/10/31 16:02
 * Description: 输入流测试类
 */
public class InputStreamTest {

    public static void main(String[] args) {
       try(InputStream inputStream = new FileInputStream("testIn.txt");
            BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)){

            byte[] buffer = new byte[1024];
            int length;
            while ((length = bufferedInputStream.read(buffer)) != -1) {
                System.out.println(new String(buffer, 0, length));
            }

        }catch (Exception e){
            e.printStackTrace();
        }
 }
}

在这个版本的改进代码中,我们将需要关闭的输入流资源的声明放在了try后的括号中,并和之前一样捕获异常,并进行合理的处理,但是我们无需再写繁复的finally代码了,编译器会自动为我们加上finally语句块。

jdk1.7及之后的版本的IO类都实现了AutoCloseable接口,并实现了close()方法,资源释放的代码就定义在该方法中,在编译时,编译器会自动将close方法中的代码添加到finally语句块中,从而提高代码的可读性。

补充:Java web中异常的优雅处理

最后作为本篇文章的补充知识,来谈谈在web开发中异常的优雅处理方式。假定开发方式是前后端分离模式开发,前后端使用json作为数据交换的格式。

先来说说在web开发中我们是如何来处理异常的,首先,我们不能直接将异常抛到页面上,将异常展示在页面上对用户来说是非常不友好的,一种好的实现方式是将整个工程中的异常统一捕获后转成包含错误码code和错误信息msg的json字符串,然后返回给前端,前端开发人员再用于展示或者其他用途。

我们的实现方式是利用Spring框架来进行异常的统一捕获和处理,下面将一步步为大家来讲解。

  1. 定义错误码和错误信息枚举类

在web开发中,异常一般包含两个部分:错误码errorCode和错误信息msg,错误码应该是全局唯一的,在程序中我们就可以根据不同的错误码区分不同的错误,错误码和错误信息一般是用一个枚举类来定义的。

/**
 * Author: listeningrain
 * Date: 2019-02-16 22:07
 * Description:错误码枚举类
 */
public enum  ErrorCodeEnmu {

    SUCCESS("SOA0000","成功"),
    SQL_NOT_EXIST_ERROR("EOS00000","数据库查询数据不存在"),
    ERR_TEST_ERROR("BEO00000","测试错误");

    private String code;
    private String msg;

    ErrorCodeEnmu(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    
    //省略getter和setter方法
}

一般来说,错误码的定义会有一个规范,例如分为:程序逻辑错误,文件相关错误,数据库相关错误等等,不同的错误分类会有不同的错误码前缀,这是由公司的开发规范所决定的。这里给出一个可供参考的例子。

       /**
         * M:表示数据信息/处理错误类
         * B:表示业务信息/处理错误类
         * S:表示系统信息/处理错误类
         * F:表示文件信息/处理错误类
         * D:表示数据库信息/处理错误类
         * Z:其他类别
         */
  1. 自定义异常

在开发中,一般会自定义我们自己的异常。

/**
 * Author: listeningrain
 * Date: 2019-02-16 22:11
 * Description: 自定义异常
 */
public class MyTestException extends RuntimeException {
    private String errorCode;
    private String msg;
    private ErrorCodeEnmu errorCodeEnmu;

    //接收错误码和错误信息的构造函数
    public MyTestException(String errorCode, String msg) {
        this.errorCode = errorCode;
        this.msg = msg;
    }

    //接收枚举类的构造函数
    public MyTestException(ErrorCodeEnmu errorCodeEnmu) {
        this.errorCode = errorCodeEnmu.getCode();
        this.msg = errorCodeEnmu.getMsg();
    }
    
    //省略getter和setter方法
  }

该异常包含两个类型为String的属性:错误码errorCode和错误信息msg,这样在构造异常的时候需要传入相应的错误码和错误信息,这些是定义在枚举类中的,为了简化构造的步骤,可以建一个接收枚举类的构造函数,再从枚举类中获取相应的错误码和错误信息。

  1. 定义错误返回对象

该对象将直接返回给前端

/**
 * Author: listeningrain
 * Date: 2019-02-16 22:19
 * Description:错误信息返回对象
 */
public class BasePojoOutput {
    private String code;
    private String msg;

    public BasePojoOutput(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    
    //省略getter和setter方法
 }
  1. 定义全局异常统一处理类

异常统一处理类,在程序中抛出的异常将在此统一捕获,再转成为json字符串返回给前端。

/**
 * Author: listeningrain
 * Date: 2019-02-16 22:15
 * Description: 异常统一处理类
 */
@ControllerAdvice(basePackages = {"wit.sqf"})
public class Exceptionhandler {

    @ExceptionHandler(MyTestException.class)
    @ResponseBody
    public BasePojoOutput handle(MyTestException e){
        return new BasePojoOutput(e.getErrorCode(),e.getMsg());
    }
}

利用Spring框架提供的@ControllerAdvice注解实现全局异常统一处理类,basePackages中定义的包路径下抛出的所有异常将会在此类中处理。

利用@ExceptionHandler(MyTestException.class)注解实现处理不同的异常,利用@ResponseBody将提取出来的异常中的错误码和错误信息转成json字符串,并返回前端。

注意:在此全局异常统一处理类中,可以将需要捕获的异常定义为Throwable.class,这样,除了我们自定义的异常,框架内部抛出的异常也会被该类所捕获,我们就可以针对程序中常见的异常进行个性化处理。这部分可以参考我的GitHub

  1. 测试

经过这样处理后,我们可以直接在程序中抛出异常,程序将直接将错误码和错误信息返回给前端。

/**
 * Author: listeningrain
 * Date: 2019-02-16 21:50
 * Description: 测试类
 */
@RestController
@RequestMapping("/index")
public class IndexController {

    @RequestMapping(value = "/test",method = RequestMethod.GET)
    public String test(){
        System.out.println("index test");
        throw new MyTestException(ErrorCodeEnmu.ERR_TEST_ERROR);
        //return "index test 哈哈";
    }
}

测试结果: result

已有 1 条评论
  1. 静心听雨

    自己占个沙发

    静心听雨 回复
发表新评论