1. 异常

异常是在程序执行过程中阻碍程序正常执行的错误操作,在 Java 语句产生异常就会创建一个异常对象。

Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。

Throwable

Throwable 是所有错误或异常的父类,它有两个直接子类 Error 和 Exception。

Error

Error是 java 运行环境内部错误或者硬件问题,不能指望程序来处理这样的问题。如Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)、 OutOfMemoryError(内存不足错误)、StackOverflowError(栈溢出错误)等。

Exception

Exception 是程序需要捕捉、需要处理的异常,是由与程序设计不完善而出现的问题,程序可以处理的问题。

它可分为RuntimeException(运行时异常)和CheckedException(可检查的异常)。

常见的RuntimeException异常:

  1. - NullPointerException 空指针异常
  2. - ArithmeticException 出现异常的运算条件时,抛出此异常
  3. - IndexOutOfBoundsException 数组索引越界异常
  4. - ClassCastException 类型转换异常
  5. - IllegalArgumentException(非法参数异常)

常见的 Checked Exception 异常:

  1. - IOException (操作输入流和输出流时可能出现的异常)
  2. - ClassNotFoundException 找不到类异常
  3. - NoSuchMethodException 方法不存在异常
  4. - NoSuchFieldException 属性不存在异常
  • Checked Exception必须被 try{}catch语句块所捕获,或者在方法通过throws子句声明,受检查的异常必须在编译时被捕捉处理
  • Unchecked Exceptions,它指编译器不要求强制处置的异常,它包括Error和RuntimeException 以及他们的子类
  • Exception 是所有被检查异常的基类,而 RuntimeException(是 Exception 的子类) 是所有不受检查异常的基类
  • 凡是继承自Exception,而不是继承自RuntimeException类的异常都是Checked Exception

处理机制

  • 异常捕捉:try…catch…finally
  • 异常抛出:throws

throw 与 throws

  • throw 语句用在方法体内,表示抛出异常,它抛出的是异常实例,所以执行throw一定抛出异常 throw new Exception()
  • throws用在方法声明后,如果有异常进行抛出,没有异常不处理,抛出的是某种类型的异常

2. 自定义异常

继承 Exception 类或其任何子类来实现自己的自定义异常类,自定义异常类可以有自定义变量和方法来传递错误代码或异常相关信息来处理异常。

  1. package com.lymn.exception;
  2. import java.io.IOException;
  3. public class TestException extends IOException {
  4. private Integer errorCode=500;
  5. public TestException(String msg, Integer errorCode){
  6. super(msg);
  7. this.errorCode = errorCode;
  8. }
  9. public Integer getErrorCode(){
  10. return this.errorCode;
  11. }
  12. }

一般情况下,自定义异常通常继承RuntimeException。

  1. @Getter
  2. public class ResultException extends RuntimeException {
  3. private Integer code;
  4. /**
  5. * 统一异常处理
  6. * @param code 状态码
  7. * @param message 提示信息
  8. */
  9. public ResultException(Integer code, String message) {
  10. super(message);
  11. this.code = code;
  12. }
  13. }
  1. @ControllerAdvice
  2. @Slf4j
  3. public class ResultExceptionHandler {
  4. // 拦截自定义异常
  5. @ExceptionHandler(ResultException.class)
  6. @ResponseBody
  7. public ResultVo resultException(ResultException e){
  8. return ResultVoUtil.error(e.getCode(), e.getMessage());
  9. }
  10. // 拦截表单验证异常
  11. @ExceptionHandler(BindException.class)
  12. @ResponseBody
  13. public ResultVo bindException(BindException e){
  14. BindingResult bindingResult = e.getBindingResult();
  15. return ResultVoUtil.error(bindingResult.getFieldError().getDefaultMessage());
  16. }
  17. // 拦截访问权限异常
  18. @ExceptionHandler(AuthorizationException.class)
  19. @ResponseBody
  20. public ResultVo authorizationException(AuthorizationException e, HttpServletRequest request,
  21. HttpServletResponse response){
  22. Integer code = ResultEnum.NO_PERMISSIONS.getCode();
  23. String msg = ResultEnum.NO_PERMISSIONS.getMessage();
  24. // 获取异常信息
  25. Throwable cause = e.getCause();
  26. String message = cause.getMessage();
  27. Class<ResultVo> resultVoClass = ResultVo.class;
  28. // 判断无权限访问的方法返回对象是否为ResultVo
  29. if(!message.contains(resultVoClass.getName())){
  30. try {
  31. // 重定向到无权限页面
  32. String contextPath = request.getContextPath();
  33. ShiroFilterFactoryBean shiroFilter = SpringContextUtil.getBean(ShiroFilterFactoryBean.class);
  34. response.sendRedirect(contextPath+shiroFilter.getUnauthorizedUrl());
  35. } catch (IOException e1) {
  36. return ResultVoUtil.error(code, msg);
  37. }
  38. }
  39. return ResultVoUtil.error(code, msg);
  40. }
  41. }

3. 异常链

异常需要封装,还需要传递异常。

异常链是一种面向对象编程技术,是将捕获的异常包装进一个新的异常中并重新抛出的异常处理方式,原异常被保存为新异常的一个属性。

package com.lymn.exception;

public class MyException1 extends Exception{
}
class MyException2 extends Exception{
    MyException2(Throwable throwable){
        super(throwable);
    }
    MyException2(){
        super();
    }
}
class A{
    public void f() throws MyException2{
        try {
            g();
        } catch (MyException1 e) {
            e.printStackTrace();
            throw new MyException2();//抛出异常2无参
            //throw new MyException2(e);//抛出异常2有参,参数传递
        }
    }
    public void g() throws MyException1{
        throw new MyException1();//抛出异常1
    }

}
class Main {
    public static void main(String[] args) {
        A a = new A();
        try {
            a.f();
        } catch (MyException2 e) {
            e.printStackTrace();
        }
    }
}

com.lymn.exception.MyException1
at com.lymn.exception.A.g(MyException1.java:24)
at com.lymn.exception.A.f(MyException1.java:16)
at com.lymn.exception.Main.main(MyException1.java:32)
com.lymn.exception.MyException2
at com.lymn.exception.A.f(MyException1.java:19)
at com.lymn.exception.Main.main(MyException1.java:32)

com.lymn.exception.MyException1
at com.lymn.exception.A.g(MyException1.java:24)
at com.lymn.exception.A.f(MyException1.java:16)
at com.lymn.exception.Main.main(MyException1.java:32)
com.lymn.exception.MyException2: com.lymn.exception.MyException1
at com.lymn.exception.A.f(MyException1.java:20)
at com.lymn.exception.Main.main(MyException1.java:32)
Caused by: com.lymn.exception.MyException1
at com.lymn.exception.A.g(MyException1.java:24)
at com.lymn.exception.A.f(MyException1.java:16)
… 1 more

4. finally 块一定会执行吗

try前抛出了异常,finally不会执行。

不终止 JVM(System.exit(0); ) 执行try的情况下 finally 中的代码一定会执行。

package com.lymn.exception;

public class TestFinally {
    //2  当在 try 块或者 catch 块里 return或者异常 时 finally 会被执行;return语句会在finally语句执行结束后执行,但是finally并不能改变返回值;而且 finally 块里也有 return 语句会把 try 块或者 catch 块里的 return 语句效果给覆盖掉且吞掉了异常。
   /* public static int test1(){
        int ret = 0;
        try{
            return ret;
        }finally{
            ret = 2;
            return ret;
        }
    }*/
   //0
    public static int test1(){
        int ret = 0;
        try{
            return ret;
        }finally{
            ret = 2;
        }
    }
    public static void main(String[] args) {
        System.out.println(TestFinally.test1());
    }
}
package com.lymn.exception;

public class TestFinally {
    /*try finally
     try块中存在return语句,那么首先也需要将finally块中的代码执行完毕,再执行return语句,而且之后的其他代码也不会再执行了
     */
    public static void method() {
        try
        {
            System.out.println("try");
            return;
        }
        catch(Exception ex)
        {
            System.out.println("异常发生了");
        }
        finally
        {
            System.out.println("finally");
        }
        System.out.println("异常处理后续的代码");
    }
    //try   先执行try块中的System.exit(0)语句,已经退出了虚拟机系统,所以不会执行finally块的代码
    public static void method1()
    {
        try
        {
            System.out.println("try");
            System.exit(0);
        }
        catch(Exception ex)
        {
            System.out.println("异常发生了");
        }
        finally
        {
            System.out.println("finally");
        }
        System.out.println("异常处理后续的代码");
    }
    public static void main(String[] args) {
       TestFinally.method();
       TestFinally.method1();
    }
}
package com.lymn.exception;

public class TestFinally {
    public static void main(String[] args) {
        try{
            throw new NullPointerException("听说你很闲,给你抛个异常。");
        }catch (NullPointerException e) {
            System.out.println("这里捕获空指针异常,提示内容:" + e.getMessage());
            e.printStackTrace();
        }
    }
}

这里捕获空指针异常,提示内容:听说你很闲,给你抛个异常。
java.lang.NullPointerException: 听说你很闲,给你抛个异常。
at com.lymn.exception.TestFinally.main(TestFinally.java:6)

package com.lymn.exception;

import java.util.concurrent.atomic.AtomicInteger;

public class TestFinally {
    //1
    public static  int test1(){
        int ret = 0;
        try{
            System.out.println(ret / 0);
             return ret;
        }catch(ArithmeticException e){
            ret++;
            return ret;
        }finally{
            ret++;
        }
    }
    //30
    public static AtomicInteger test3(AtomicInteger a){
        try{
            System.out.println(a.get() / 0);
            a.addAndGet(10);
        }catch(ArithmeticException e){
            a.addAndGet(10);
            return a;
        }finally{
            a.addAndGet(10);
        }
        return a;
    }
    public static void main(String[] args) {
        System.out.println(TestFinally.test1());
        AtomicInteger a = new AtomicInteger(10);
        System.out.println(test3(a).get());
    }
}

5. NoClassDefFoundError 和 ClassNotFoundException

NoClassDefFoundError 是一个 Error 类型异常,由 JVM 引起,不应该尝试捕获这个异常。

ClassNotFoundException 是一个受查异常,需要显式使用 try-catch 对其进行捕获和处理,或在方法签名中用 throws 关键字进行声明。

6. 注意事项

  • 受检异常,调用时要么捕获、要么声明;运行异常调用时不需要声明异常
  • java 中重写方法要么不声明异常,要么抛出的异常不能是父方法抛出异常的父类
  • 如果父类多个异常,子类不能抛出父类没有的异常类型
  • 被重写的方法没有声明异常,子类方法绝对不可以声明异常
  • RuntimeException异常不应该通过catch 的方式来处理
  • 捕获异常时尽量捕获具体的异常类型而不要直接捕获其父类
  • 方法定义中 throws 后面尽量定义具体的异常列表
  • catch 块时要保证异常类型的优先级书写顺序,要保证子类靠前父类靠后的原则
  • 不要捕获 Throwable 类
  • 方法没有抛出受检查类型异常则在调用方法的地方就不能主动添加受检查类型异常捕获,但是可以添加运行时异常或者 Exception 捕获
  • 避免在 finally 中使用 return 语句或者抛出异常
  • 不要使用异常控制程序的流程
  • catch 如果真的没必要处理则至少加行打印,方便排查问题
  • 接口方法抛出的异常尽量保证是运行时异常类型,除非迫不得已才抛出检查类型异常
  • 方法返回值尽量不要使用 null(特殊场景除外),这样可以避免很多 NullPointerException 异常,防止NPE是程序员的基本修养

  • 有try块放到了事务代码中,catch异常后,如果需要回滚事务,一定要注意手动回滚事务
  • finally块必须对资源对象、流对象进行关闭,有异常也要做try-catch。 说明:如果JDK7及以上,可以使用try-with-resources方式
  • 避免出现重复的代码(Don’t Repeat Yourself),即DRY原则。抽取共性方法,或者抽象公共类,甚至是组件化

7. 异常体系图

异常 - 图1

参考

java中关于try、catch、finally中的细节分析,值得一看,好文