本篇主要是记录自己所理解的 Java 异常处理机制(基于 jdk1.7)以及怎么去处理和设计异常。还记得当初学习 Java 异常这块的时候都没怎么注意它的用途,以为就是简单的处理下异常,我避免程序出现这样错误就行了(当初真的很懵懂很天真)。当踏入社会工作后才知道一个好的异常处理框架是有多重要,比方说当你的项目投入使用的时候,当程序出错如何快速的定位到问题的根源(出了什么错,在哪出的错,为什么出错),这就跟你的异常处理的好坏有关了(当然离不开你的日志处理)。在有效使用异常的情况下,异常类型会回答报的什么错,异常堆栈跟踪回答在哪出的错,异常信息会回答为什么出错。所以如果设计和处理的不好,将会花费大量时间去维护(出现这样的情况公司肯定是不愿意的同时也会质疑你的能力了)。
本篇将从下面几个方面进行记录,如果发现有错误的地方还请各位大佬们指出以便改进,谢谢。
首先你得明白异常是什么,为什么发生异常。通俗的讲,程序异常就是程序出现了错误,而这种错误可能是逻辑上的错误也可能是系统上的错误。在 Java 中把异常当做对象处理,所以我们要了解在 java 中有哪些可以类是用来描述异常的,它的层次结构图如下(只描述写重要的类):
在 java 中,所有的异常都是通过 Throwable 类及其子类传播的(从上面层次结构图中可以看出)。根据层次关系我们依次来分析:
- /**
- * The class {@code Exception} and its subclasses are a form of
- * {@code Throwable} that indicates conditions that a reasonable
- * application might want to catch.
- *
- * <p>The class {@code Exception} and any subclasses that are not also
- * subclasses of {@link RuntimeException} are <em>checked
- * exceptions</em>. Checked exceptions need to be declared in a
- * method or constructor's {@code throws} clause if they can be thrown
- * by the execution of the method or constructor and propagate outside
- * the method or constructor boundary.
- *
- * @author Frank Yellin
- * @see java.lang.Error
- * @jls 11.2 Compile-Time Checking of Exceptions
- * @since JDK1.0
- */
- public class Exception extends Throwable {
根据上面 Excepiton 类还可以分成这两大类:运行时异常和非运行时异常。
在 java 中异常处理机制大致为:先抛出异常再捕捉异常。抛出异常一般是交给 java 虚拟机,当然也可以自己手动抛出异常(下面会涉及到),重点是怎么捕捉异常。
1、捕获异常(try、catch、finally)
捕获异常的形式可能是上面三个的组合,组合形式有:try..catch、try...finally 和 try..catch..finally
主要记录下 try..catch..finally 的主要事项:
1.1、其中 catch 块可以有多个,但要注意捕获异常类的顺序:子类一定要在父类前,否则会编译不通过。
- @Test
- public void test1() {
- int[] arr = {1,3,5,0};
- try{
- for (int i = 0; i < arr.length; i ++) {
- System.out.println(10/arr[i]);
- }
- }catch (Exception e){
- e.printStackTrace();
- }catch (ArithmeticException e){//编译不通过。因为异常在前面的catch块中捕获。
- e.printStackTrace();
- }finally{
- System.out.println("end");
- }
- }
1.2 当 catch 块中有 return 时要注意其执行顺序:finally 语句在 return 返回之前执行。
- public class ExceptionTest {
- @Test
- public void test1() {
- System.out.println("计算结果:" + sum());
- }
- public int sum() {
- int count = 0;
- int[] arr = {1,3,5,0};
- try{
- for (int i = 0; i < arr.length; i ++) {
- count += 10/arr[i];
- }
- }catch (ArithmeticException e){
- System.out.println("计算失败,分母不能为0:" + e.getMessage());
- return count;
- }finally{
- System.out.println("sum() end");//会先执行
- }
- return count;
- }
- }
ouput:
- 计算失败,分母不能为0:/ by zero
- sum() end
- 计算结果:15
1.3 当 catch 块中有 return 时要注意 fianlly 语句对其返回值进行修改时是否影响其返回值:基本类型及其字符串类型是不会被改变的,对象类型下成员变量的值是可以改变的。
- public class ExceptionTest {
- @Test
- public void test1() {
- System.out.println("基本类型mod1()=" + mol1());
- System.out.println("字符串类型mod2()=" + mol2());
- System.out.println("对象类型mod3()=" + mol3());
- }
- public int mol1() {
- int count = 0;
- int[] arr = {1,3,5,0};
- try{
- for (int i = 0; i < arr.length; i ++) {
- arr[i] = 10/arr[i];
- }
- }catch (ArithmeticException e){
- return count;
- }finally{
- //基本类型修改无效,其实这相当于一个局部变量
- count = -1;
- }
- return count;
- }
- public String mol2() {
- String msg = "success";
- int[] arr = {1,3,5,0};
- try{
- for (int i = 0; i < arr.length; i ++) {
- arr[i] = 10/arr[i];
- }
- }catch (ArithmeticException e){
- msg = "fail";
- return msg;
- }finally{
- //字符串修改无效,其实这相当于一个局部变量
- msg = "fail, finally";
- }
- return msg;
- }
- public StringBuffer mol3() {
- StringBuffer msg = new StringBuffer();
- int[] arr = {1,3,5,0};
- try{
- for (int i = 0; i < arr.length; i ++) {
- arr[i] = 10/arr[i];
- }
- }catch (ArithmeticException e){
- msg.append("fail");
- return msg;
- }finally{
- //对象类型,其成员变量char[] value;的值是可以被改变的
- msg.append(", finally");
- }
- return msg;
- }
- }
output:
- 基本类型mod1()=0
- 字符串类型mod2()=fail
- 对象类型mod3()=fail, finally
1.4 当 finally 语句中有返回值时,会覆盖 catch 中的返回语句,也会使 catch 块的 throw 抛出的异常失效。(其实不建议使用的)
- public class ExceptionTest {
- @Test
- public void test1() {
- System.out.println("基本类型mod1()=" + mol4());
- System.out.println("字符串类型mod2()=" + mol5());
- }
- public int mol4() {
- int count = 0;
- int[] arr = {1,3,5,0};
- try{
- for (int i = 0; i < arr.length; i ++) {
- arr[i] = 10/arr[i];
- }
- }catch (ArithmeticException e){
- return count;
- }finally{
- count = -1;
- return count;//直接返回值,不会在进入catch块中执行
- }
- }
- public String mol5() {
- String msg = "success";
- int[] arr = {1,3,5,0};
- try{
- for (int i = 0; i < arr.length; i ++) {
- arr[i] = 10/arr[i];
- }
- }catch (ArithmeticException e){
- throw new ArithmeticException("分母不能为0");
- }finally{
- //字符串修改无效,其实这相当于一个局部变量
- msg = "fail, finally";
- return msg;//直接返回值,不会在进入catch块抛出异常
- }
- //return msg;
- }
- }
output:
- 基本类型mod1() = -1字符串类型mod2() = fail,
- finally
2、抛出异常,有两种方式:使用 throws 指定异常,在方法上使用;可以使用 throw new 的方法,在方法里使用。不要搞混淆就行。
1、只针对异常的情况才使用异常,不要用异常去控制正常流程。(这点我想大部分人不会这么做,但是也列举出来,如果这么做了会引起莫名奇妙的问题)
2、避免不必要的使用受检异常。
3、优先使用标准异常,即错误信息相对应的异常,不要乱抛出异常类否则会找不到问题的根源。比方说有个地方是数组索引超限,你却抛出个参数非法异常,这样就掩盖了原始异常信息。
4、不要忽略异常,即不要使用空 catch 块。(没有异常信息,就无法找出问题的所在)
5、尽量捕获写细节信息。比方说数组索引越界,它是超过最大索引还是小于 0?
来源: https://www.cnblogs.com/yuanfy008/p/8110897.html