Java异常机制

一、异常简介

异常本质上是程序上的错误,包括程序逻辑错误和系统错误。通过异常机制,我们可以更好地提升程序的健壮性

二、异常体系结构

Java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有异常的超类,所有异常类型都是内置类Throwable的子类。

Throwable两个不同的分支

  • Error类对象由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。例如,Java虚拟机运行错误(Virtual MachineError),当JVM不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError,发生这些异常时,JVM会选择终止程序
  • Exception表示用户程序可能捕捉的异常情况或者说是程序可以处理的异常

Exception两个不同分支

  • checked exception 受检异常,也称作非运行期异常或者编译异常
  • unchecked exception 非受检异常,也称作运行时异常(RuntimeException)

两者异同点:

  • 受检异常种类:IOExeption,SQLException
  • 非受检异常种类:ArrayIndexOutOfBoundsException(数组下标越界)、NullPointerException(空指针异常)、ArithmeticException(算术异常)、MissingResourceException(丢失资源)、ClassNotFoundException(找不到类)等异常
  • 受检异常从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过
  • 非受检异常程序中可以选择捕获处理,也可以不处理,不处理的话最终会抛出到虚拟机,导致虚拟机终止程序。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生

三、异常处理机制

Java的异常处理本质上是抛出异常和捕获异常

抛出:

  • 方法上使用throws,表示该方法有可能抛出异常
  • 代码内抛出异常对象 throw new Exeception()

捕获:
使用try/catch/finally

异常处理流程:
1、当程序出现异常,JVM自动根据异常的类型实例化一个与之类型匹配的异常类对象。
2、根据异常对象判断是否存在异常处理。如果不存在异常处理,则由JVM对异常默认处理:输出异常信息,结束程序调用。
3、如果存在异常捕获操作,try语句捕获异常类实例化对象,再与catch语句进行异常类型匹配,并处理异常。
4、不管是否匹配到catch语句,如果存在finally语句,就会执行finally语句代码。
5、finally语句后面的代码根据之前是否匹配到catch语句进行处理。如果匹配到catch语句,也即捕获到异常,则继续执行finally后的语句。如果没有匹配到catch语句,则将异常交由JVM默认处理。

四、关键字 try catch finally 执行流程

问题1: try{} 里有一个 return 语句,那么紧跟在这个 try 后的 finally{} 里的 code 会不会被执行,什么时候被执行,在 return 前还是后?

回答: 会执行,在方法返回调用者前(即return前面)执行

public int test() {
    try {
        int i = 1;
        return i;
    } catch (Exception e) {
        return -1;
    } finally{
        System.out.println(-2);
    }
}

结果:
	-2
	返回值还是-1

问题2: try,catch,finally语句中都有return语句,那么执行流程是啥?

回答: finally语句一定会执行,并且finally语句的return语句会覆盖try和catch语句的return值

public int test() {
    try {
        int i = 1;
        i = i / 0;
        return i;
    } catch (Exception e) {
        return -1;
    } finally{
        return -2;
    }
}

结果: -2

这里如果注释掉 i = i / 0;,还是会返回-2,原因上面已经说过

五、自定义异常

public class SelfException extends RuntimeException{
    
    //自定义异常编码
    private final String errorCode;

    public SelfException(String errorCode) {
        super();
        this.errorCode = errorCode;
    }

    public SelfException(String errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

    public SelfException(String errorCode, Throwable cause) {
        super(cause);
        this.errorCode = errorCode;
    }

    public SelfException(String errorCode, String message, Throwable cause) {
        super(message, cause);
        this.errorCode = errorCode;
    }

    public String getErrorCode() {
        return errorCode;
    }


}

当java提供的异常类型不能满足开发需要时,可以自己开发一个异常类,规定不同类型的异常编码。实现自定义异常类,只需要继承Exception或RuntimeException父类即可

比如在代码有会有很多参数校验,就可以自定义异常,给定异常编码,然后在校验参数的方法中抛出自定义异常接口

public class SelfException extends RuntimeException{

    //自定义异常编码
    private final String errorCode;

    public SelfException(String errorCode) {
        super();
        this.errorCode = errorCode;
    }

    public SelfException(String errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

    public SelfException(String errorCode, Throwable cause) {
        super(cause);
        this.errorCode = errorCode;
    }

    public SelfException(String errorCode, String message, Throwable cause) {
        super(message, cause);
        this.errorCode = errorCode;
    }

    public String getErrorCode() {
        return errorCode;
    }
}

public class ValidateParameter {
    public static void main(String[] args) {
        String param = "";
        try {
            validate(param);
        } catch (SelfException e) {
            System.out.println(e.getErrorCode()+ "  "+e.getMessage());
        }
    }

    private static void validate(String parameter) {
        if("".equals(parameter)) {
            throw new SelfException("EXP001", "参数非空异常");
        }
    }
}

运行结果:
EXP001  参数非空异常

自定义异常类SelfException,参数检验不通过抛出该异常对象,使用try/catch捕获该异常,可以获取自定义的异常编码和异常信息。

Tips:
我们可以使try/catch捕获程序抛出的异常,在catch代码块中转化成自定义异常,保留异常堆栈信息,然后抛出自定义异常,在最上层捕获该异常,将其异常编码和信息封装入接口返回值,这样可以让接口调用间异常处理更加简洁

六、异常使用注意事项

1.只在必要使用异常的地方才使用异常
不要用异常去控制程序的流程,这是因为新建异常对象比创建一个普通对象是要更加的耗时。

2.子类重写父类方法的时候,如何确定异常抛出声明的类型

  • 父类的方法没有声明异常,子类在重写该方法的时候不能声明异常;
  • 如果父类的方法声明一个异常exception,则子类在重写该方法的时候声明的异常不能是exception的父类;
  • 如果父类的方法声明的异常类型只有非运行时异常(运行时异常),则子类在重写该方法的时候声明的异常也只能有非运行时异常(运行时异常),不能含有运行时异常(非运行时异常)

3.切忌使用空catch块
在捕获了异常之后什么都不做,相当于忽略了这个异常。千万不要使用空的catch块,空的catch块意味着你在程序中隐藏了错误和异常,并且很可能导致程序出现不可控的执行结果。如果你非常肯定捕获到的异常不会以任何方式对程序造成影响,最好用Log日志将该异常进行记录,以便日后方便更新和维护

4.注意catch块的顺序
不要把父类的异常放在最前面的catch块

try {
        FileInputStream inputStream = new FileInputStream("");
        int ch = inputStream.read();
        System.out.println("aaa");
        return "step1";
    } catch (IOException e) {
        System.out.println("io exception");        
         return "step2";
    }catch (FileNotFoundException e) {
        System.out.println("file not found");          
        return "step3";
    }finally{
        System.out.println("finally block");
        //return "finally";
    }

第二个catch的FileNotFoundException将永远不会被捕获到,因为FileNotFoundException是IOException的子类

5.异常处理尽量放在高层进行
尽量将异常统一抛给上层调用者,由上层调用者统一之时如何进行处理。如果在每个出现异常的地方都直接进行处理,会导致程序异常处理流程混乱,不利于后期维护和异常错误排查。由上层统一进行处理会使得整个程序的流程清晰易懂

6.避免多次在日志信息中记录同一个异常
只在异常最开始发生的地方进行日志信息记录。很多情况下异常都是层层向上跑出的,如果在每次向上抛出的时候,都Log到日志系统中,则会导致无从查找异常发生的根源

七、异常信息打印

使用e.getMessage()来获取异常信息,但是这样获取到的信息内容并不全,而且有时候为空,比如NullPointerException空指针异常,就不能获取堆栈信息。

可以使用下面代码获取:

public static String getStackTrace(Throwable ex, int len) {
        StringWriter stringWriter = new StringWriter();

        String retStr = null;
        try (PrintWriter printWriter = new PrintWriter(stringWriter)) {
            ex.printStackTrace(printWriter);
            retStr = stringWriter.toString();
            if (null != retStr && retStr.length() >= len) {
                retStr = retStr.substring(START_NUM_0, len);
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }

        return retStr;
    }

将异常堆栈信息写入到PrintWriter 流中,转换成StringWriter再转换成String字符串。我们传入异常对象e和长度len两个参数,就可以获取我们需要的异常堆栈信息,然后保存到服务器日志。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值