java中的异常(带你全面了解异常)

简介: java中的异常(带你全面了解异常)

1.Java的异常体系

Java 内置了丰富的异常体系, 用来表示不同情况下的异常.


下图表示 Java 内置的异常类之间的继承关系:

image.png



来看下图:

image.png



编译时异常意味着如果不处理这个异常的话,编译时就不能通过,直接飘红


运行时异常是运行的时候才发生的异常


2.异常的核心思想

异常的核心思想就是EAFP.


EAFP:It's Easier to Ask Forgiveness than Permission. "事后获取原谅比事前获取许可更容易". 也就是先操作, 遇到问题再处理.


所以说异常的核心思想其实就是程序先执行,出现异常了再去解决


我们来看一段EAFP风格的代码,稍后会对这段代码进行解释:


try {
登陆游戏();
开始匹配();
游戏确认();
选择英雄();
载入游戏画面();
...
} catch (登陆游戏异常) {
处理登陆游戏异常;
} catch (开始匹配异常) {
处理开始匹配异常;
} catch (游戏确认异常) {
处理游戏确认异常;
} catch (选择英雄异常) {
处理选择英雄异常;
} catch (载入游戏画面异常) {
处理载入游戏画面异常;
}


3.异常的基本用法

3.1捕获异常

基本语法

try{

有可能出现异常的语句 ;

}[catch (异常类型 异常对象) {

} ... ]

[finally {

异常的出口

}]

1:try 代码块中放的是可能出现异常的代码.
2:catch 代码块中放的是出现异常后的处理行为.
3:catch括号总放入的是可能会出现的异常类型和异常对象
4:finally 代码块中的代码用于处理善后工作, 会在最后执行.
5:其中 catch 和 finally 都可以根据情况选择加或者不加.


3.2代码示例

代码1 使用try catch来处理异常

1:假设此时我们不用try catch来处理异常


public class yichang {
    public static void main(String[] args) {
        int a = 0;
        System.out.println(10 / a);
        System.out.println("hh");
    }
}

运行后我们会发现此时最后一句输出语句根本不会被执行,原因是System.out.println(10/a)这条语句发生了算数异常,所以根本不会执行 System.out.println("hh"); 这条语句.


总结:假如不使用try catch处理异常的话,便会交给jvm处理异常,最终程序会异常终止,且不会继续执行发生异常之后的代码.


当使用try catch来处理异常后


public class yichang {
    public static void main(String[] args) {
        int a = 0;
        try {
            //try中存放的是可能会发生异常的代码
            System.out.println(10 / a);
            System.out.println("hhhh");
        } catch (ArithmeticException e) {
            //catch代码块放入的是处理异常的代码
        }
        System.out.println("hh");
    }
}
    //输出结果为
    hh

我们会发现当我们使用了try catch来处理了这个算术异常后,此时输出结果为hh.


同样System.out.println("hhhh")这句话不会执行,原因是在System.out.println(10/a)处发生了异常.


在来看一段代码:


public class yichang {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try {
            System.out.println("before");
            System.out.println(arr[100]);
            System.out.println("after");
        } catch (ArrayIndexOutOfBoundsException e) {
            // 打印出现异常的调用栈
            e.printStackTrace();
        }
        System.out.println("after try catch");
    }
}
     //输出结果为
     before
     java.lang.ArrayIndexOutOfBoundsException: 100 at yichang.main(yichang.java:10)
     after try catch


e.printStackTrace为打印出现异常的调用栈,所以就会出现输出结果中的第二行的话


代码2 catch 只能处理对应种类的异常

继续来看下面的代码,我们可以看到此时try中的代码会发生空指针异常,但是catch中捕获的却是数组越界异常,那么最终的System.out.println("after try catch");这条语句是不会被执行到的,原因是catch中所捕获的异常类型与实际发生的异常不匹配,所以处理异常失败。


public class yichang {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        try {
            System.out.println("before");
            arr = null;
            System.out.println(arr[100]);
            System.out.println("after");
        } catch (ArrayIndexOutOfBoundsException e) {
            /*e.printStackTrace();*/
        }
        System.out.println("after try catch");
    }
}

最终输出结果为:


Exception in thread "main" java.lang.NullPointerException at yichang.main(yichang.java:11)
before


代码3 catch可以捕获多个异常

方法1:


public class yichang {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        //catch代码块可以捕获多个异常
        try {
            System.out.println("before");
            System.out.println(arr[100]);
            //因为此时上面的代码发生了异常,所以此时下面的代码是不会被执行的。
            System.out.println("after");
        } catch (ArithmeticException e) {
            e.printStackTrace();
            System.out.println("此段代码不会执行");
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
            System.out.println("捕获了数组越界异常");
        }
        System.out.println("after try catch");
    }
}
    //输出结果为
    before
    捕获了数组越界异常
    after try catch
    java.lang.ArrayIndexOutOfBoundsException: 100 at yichang.main(yichang.java:11)


假如此时try中的代码发生的是数组下标越界异常,但是第一个catch中捕获的却是算术异常,那么这个catch中的代码是不会被执行的,第二个catch捕获的是数组下标越界异常,那么就会正常执行里面的代码,最后再执行System.out.println("after try catch");这条语句.


方法2:


除了可以像上述那样捕获多个异常以外,我们还可以通过|这个符号来捕获多个异常


如果多个异常的处理方式是完全相同, 也可以写成这样:


catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
...
}
public class yichang {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        //catch代码块可以捕获多个异常
        try {
            System.out.println("before");
            System.out.println(arr[100]);
            //因为此时上面的代码发生了异常,所以此时下面的代码是不会被执行的。
            System.out.println("after");
        } catch (ArithmeticException | ArrayIndexOutOfBoundsException e) {
            System.out.println("此段代码不会执行");
            e.printStackTrace();
        }
        System.out.println("after try catch");
    }
}
//输出结果为
     before
     此段代码不会执行
     java.lang.ArrayIndexOutOfBoundsException: 100 at yichang.main(yichang.java:11)
     after try catch

代码4 也可以用一个 catch 捕获所有异常(不推荐)

public class yichang {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        //也可以用一个 catch 捕获所有异常(不推荐)
        try {
            System.out.println("before");
            System.out.println(arr[100]);
            //因为此时上面的代码发生了异常,所以此时下面的代码是不会被执行的。
            System.out.println("after");
            //由于 Exception 类是所有异常类的父类. 因此可以用这个类型表示捕捉所有异常.
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("此段代码会执行");
        }
    }
}
//输出结果
before
java.lang.ArrayIndexOutOfBoundsException: 100 at yichang.main(yichang.java:11)
此段代码会执行

由于 Exception 类是所有异常类的父类. 因此可以用这个类型表示捕捉所有异常.


代码5 finally

finally表示最后的善后工作, 例如释放资源.


scanner对象此时表示的是一种资源,资源最终要被释放


public class yichang {
    public static void main(String[] args) {
        //scanner中的close方法
        Scanner scanner = new Scanner(System.in);
        try {
            int a = scanner.nextInt();
            System.out.println(10 / a);
        } catch (ArithmeticException e) {
            e.printStackTrace();
        } finally {
            scanner.close();
            System.out.println("不管是否发生异常,finally块都会永远被执行");
        }
    }
}
//输出结果
java.lang.ArithmeticException: / by zero at yichang.main(yichang.java:13)
不管是否发生异常,finally块都会永远被执行

注意:无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行). 来看如下代码


public class yichang {
    public static void main(String[] args) {
        int a=0;
        try {
            int ret=10/a;
        }catch (ArrayIndexOutOfBoundsException e){
        }finally {
            System.out.println("hhhh");
        }
    }
}

输出结果为:


hhhh
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at yichang.main(yichang.java:9)


可以看到,此时我们并没有在catch中找到匹配的异常类型,所以catch中的语句并不会执行,但是finally中的语句是无论是否找到匹配的异常类型, finally 中的代码都会被执行到,所以最终先输出hhh,再抛出算术异常.


代码6 使用try负责回收资源

刚才在代码5中,我们使用finally来回收资源,现在还有一种等价写法, 将 Scanner 对象在 try 的 ( ) 中创建, 就能保证在 try 执行完毕后自动调用 Scanner的 close 方法.最终在finally中也不再需要去调用close方法.


public class yichang {
    public static void main(String[] args) {
        try(Scanner scanner = new Scanner(System.in)) {
            int a = scanner.nextInt();
            System.out.println(10 / a);
        } catch (ArithmeticException e) {
            e.printStackTrace();
        } finally {
            System.out.println("不管是否发生异常,finally块都会永远被执行");
        }
    }
}
//输出结果
0
java.lang.ArithmeticException: / by zero at yichang.main(yichang.java:11)

不管是否发生异常,finally块都会永远被执行


代码7 建议finally中不要使用return语句

public class yichang {
    public static int func() {
        try {
            int a = 10;
            int ret = 10 / a;
            return ret;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            return 10;
        }
    }
    public static void main(String[] args) {
        System.out.println(func());
    }
}
//输出结果
10


原因是当我们调用func函数其实最想获得的是10/a的返回值,但是当我们在func函数中使用了finally之后,func函数的返回值变为了finally函数中return出来的值,不是我们想要的结果,所以最终输出结果不是3,而是10.所以我们并不建议在finally中使用return语句.


代码8 如果本方法中没有合适的处理异常的方式, 就会沿着调用栈向上传递

   在下面代码中,首先func方法并没有处理数组下标越界异常,所以此时会沿着栈向上传递到main方法,最终在main方法中找到了try catch来处理数组下标越界这个异常.


image.png


public class yichang {
    public static void func3() {
        int[] arr = {1, 2, 3};
        System.out.println(arr[100]);
    }
    public static void main(String[] args) {
        try {
            func3();
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        System.out.println("after try catch");
    }
}
//输出结果
java.lang.ArrayIndexOutOfBoundsException: 100
  at yichang.func3(yichang.java:8)
  at yichang.main(yichang.java:13)
after try catch

代码示例9 如果向上一直传递都没有合适的方法处理异常, 最终就会交给 JVM 处理, 程序就会异常终止(和我们最开始未使用 try catch 时是一样的)

public class yichang {
    public static void func() {
        int[] arr = {1, 2, 3};
        System.out.println(arr[100]);
    }
    public static void main(String[] args) {
        func();
        System.out.println("hhh");
    }
}

输出结果为


Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
    at yichang.func(yichang.java:8)
    at yichang.main(yichang.java:13)

可以看到, 程序已经异常终止了, 没有执行到 System.out.println("after try catch"); 这一行.


总结

异常处理流程


1:程序先执行 try 中的代码.


2:如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配. 如果找到匹配的异常类型, 就会执行 catch 中的代码.


3:如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者.


4:无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行). 如果上层调用者也没有处理的了异常, 就继续向上传递.


5:一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止.


3.3声明和抛出异常

声明异常:使用throws关键字


抛出异常:使用throw关键字


   一般声明异常和抛出异常都是搭配使用的,一个方法执行体中抛出了异常后,要在这个方法的后面声明抛出了哪些异常,这样是为了告诉方法的调用者这个方法声明了哪些异常,好让方法的调用者根据声明的不同异常来做出相应的处理操作,下面来看代码:

public class yichang {
    public static void func(int y) throws ArithmeticException,ArrayIndexOutOfBoundsException {
        if(y==0){
            throw new ArithmeticException();
        }
        System.out.println(10/y);
    }
    public static void main(String[] args) {
        try {
            func(0);
        } catch (ArithmeticException|ArrayIndexOutOfBoundsException e) {
            System.out.println("jj");
        } finally {
            System.out.println("hh");
        }
    }
}

3.4异常小练习

第1题(单选题)

题目名称


关于Java的异常处理机制的叙述哪些正确?


题目内容


A.如果程序发生错误及捕捉到异常情况了,才会执行finally部分


B.其他选项都不正确


C.当try区段的程序发生异常且被catch捕捉到时,才会执行catch区段的程序


D.catch部分捕捉到异常情况时,才会执行finally部分


正确答案:C

第2题(单选题)

题目名称

有关下述Java代码描述正确的选项是____。

public class TestClass {
  private static void testMethod() {
     System.out.println("testMethod");
   }
  public static void main(String[] args) {
     ((TestClass)null).testMethod();
   }
}

题目内容


A.编译不通过


B.编译通过,运行异常,报NullPointerException


C.编译通过,运行异常,报IllegalArgumentException


D.编译通过,运行异常,报NoSuchMethodException


E.编译通过,运行异常,报Exception


F.运行正常,输出testMethod

第3题(单选题)

题目名称

下列程序的运行结果

public void getCustomerInfo() {
    try {
      // do something that may cause an Exception
    } catch (java.io.FileNotFoundException ex) {
      System.out.print("FileNotFoundException!");
    } catch (java.io.IOException ex) {
      System.out.print("IOException!");
    } catch (java.lang.Exception ex) {
      System.out.print("Exception!");
    }
  }

题目内容

A.IOException!

B.IOException!Exception!

C.FileNotFoundException!IOException!

D.FileNotFoundException!IOException!Exception!

正确答案:A


解析:从题中我们可以看到Exception这个异常是IOException这个异常的父类,而IOException是FileNotFoundException这个异常的父类,所以从整个代码来看的话,从上到下构成了子类到父类这样一个顺序,当try中假设抛出了FileNotFoundException这个异常的话,最终只会抛出对应 catch (java.io.FileNotFoundException ex)中的语句,并不会将catch (java.io.IOException ex)和 catch (java.lang.Exception ex)这两个catch中的语句抛出。

反例:假设此时我们变化catch的顺序,那么最终会发生什么呢?来看代码吧:

public void getCustomerInfo() {
        try {
            // do something that may cause an Exception
        } catch (java.lang.Exception ex) {
            System.out.print("Exception!");
        } catch (java.io.IOException ex) {
            System.out.print("IOException!");
        } catch (java.io.FileNotFoundException ex) {
            System.out.print("FileNotFoundException!");
        }
    }

当我们变化顺序后,当try中假设抛出了FileNotFoundException这个异常的话,最终只会抛出对应 catch (java.lang.Exception ex)中的语句,并不会将catch (java.io.IOException ex)和 catch (java.lang.FileNotFoundException ex)这两个catch中的语句抛出。


原因是Exception是FileNotFoundException的父类,父类已经捕捉到这个异常了,下面的catch语句当然不用再就继续捕捉了,且不管顺序如何,构成子父类关系的catch语句也只有一个会被执行


总结:以后捕捉异常的时候,从上往下,一定注意顺序,如果最上面是父类,那么下面的捕捉语句都不会被执行,所以应该先写子类的catch语句,再写父类的catch语句。

第5题(单选题)

题目名称

下面有关JAVA异常类的描述,说法错误的是?

题目内容

A. 异常的继承结构:基类为Throwable,Error和Exception继承Throwable,RuntimeException和IOException等继承Exception


B.非RuntimeException一般是外部错误(非Error),其必须被 try{}catch语句块所捕获


C. Error类体系描述了Java运行系统中的内部错误以及资源耗尽的情形,Error不需要捕捉


D.RuntimeException体系包括错误的类型转换、数组越界访问和试图访问空指针等等,必须被 try{}catch语句块所捕获

正确答案:D


解析:


A:正确


B:非RuntimeException包括了Error和编译时异常,然后又加上非Error后,就只剩下了编译时异常,编译时异常是必须被try catch捕获的


C:正确


D:运行时异常不一定非要被try catch捕获,不去捕获的话便会交给JVM去处理

4.自定义异常

注意事项

1:自定义异常通常会继承自 Exception 或者 RuntimeException.

2:继承自 Exception 的异常默认受查异常(编译时异常).

3:继承自 RuntimeException 的异常默认非受查异常(运行时异常).

代码实例

代码1:手动定义异常原因

在手动抛出异常的时候,可以说明产生这个异常的原因,在异常后面的括号内写入原因即可

public class yichang {
    public static void main(String[] args) {
        try {
            throw new ArithmeticException("抛出算术异常");
        } catch (ArithmeticException e) {
            e.printStackTrace();
        }
    }
}

输出结果为:

java.lang.ArithmeticException: 抛出算术异常

   at yichang.main(yichang.java:14)

代码2:自己定义一个异常类

第一种情况:自己定义的这个异常类继承于RuntimeException

class MyException extends RuntimeException {
    public MyException(String message) {
        super(message);
    }
}
public class yichang {
    public static void main(String[] args) {
        try {
            throw new MyException("抛出自己定义的异常");
        } catch (MyException e) {
            e.printStackTrace();
        }
    }
}

输出结果为:

MyException: 抛出自己定义的异常

   at yichang.main(yichang.java:16)

第二种情况:自己定义的这个异常类继承于Exception

先来看两组代码:

class MyException extends RuntimeException {
    public MyException(String message) {
        super(message);
    }
}
public class yichang {
    public static void main(String[] args) {
        int a=10;
        if(a==10){
            throw new MyException("sss");
        }
    }
}

运行结果为:

Exception in thread "main" MyException: sss

   at yichang.main(yichang.java:15)

class MyException extends Exception {
    public MyException(String message) {
        super(message);
    }
}
public class yichang {
    public static void main(String[] args) {
        int a=10;
        if(a==10){
            throw new MyException("sss");
        }
    }
}

运行结果报错:截图如下

image.png

可以看到,当我们自定义的异常类继承于RuntimeException,且在主方法中并没有使用try catch捕获我们自定义的异常的时候,最终会将异常交给我们的JVM处理,因为运行时异常既可以被try catch捕获,也可以交给JVM处理。


当我们自定义的异常类继承于Exception,且在主方法中并没有使用try catch捕获我们自定义的异常的时候,最终会报错,原因是继承于Exception的异常类属于编译时异常,也就是我们的受查异常,必须被try catch捕获。


同时我们还要注意:

代码示例:

class MyException extends Exception {
    public MyException(String message) {
        super(message);
    }
}
public class yichang {
    public static void print(int a) throws MyException {
        if (a == 0) {
            throw new MyException("编译时异常");
        }
    }
    public static void main(String[] args) {
        try {
            print(0);
        } catch (MyException e) {
            System.out.println("hh");
        }
    }
}
class MyException extends Exception {
    public MyException(String message) {
        super(message);
    }
}
public class yichang {
    public static void print(int a) {
        try {
            if (a == 0) {
                throw new MyException("编译时异常");
            }
        } catch (MyException e) {
            System.out.println("hh");
        }
    }
    public static void main(String[] args) {
        print(0);
    }
}
相关文章
|
25天前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
48 1
|
23天前
|
Java API 调度
如何避免 Java 中的 TimeoutException 异常
在Java中,`TimeoutException`通常发生在执行操作超过预设时间时。要避免此异常,可以优化代码逻辑,减少不必要的等待;合理设置超时时间,确保其足够完成正常操作;使用异步处理或线程池管理任务,提高程序响应性。
52 12
|
25天前
|
Java
在 Java 中,如何自定义`NumberFormatException`异常
在Java中,自定义`NumberFormatException`异常可以通过继承`IllegalArgumentException`类并重写其构造方法来实现。自定义异常类可以添加额外的错误信息或行为,以便更精确地处理特定的数字格式转换错误。
30 1
|
26天前
|
IDE 前端开发 Java
怎样避免 Java 中的 NoSuchFieldError 异常
在Java中避免NoSuchFieldError异常的关键在于确保类路径下没有不同版本的类文件冲突,避免反射时使用不存在的字段,以及确保所有依赖库版本兼容。编译和运行时使用的类版本应保持一致。
60 7
|
27天前
|
Java 编译器
如何避免在 Java 中出现 NoSuchElementException 异常
在Java中,`NoSuchElementException`通常发生在使用迭代器、枚举或流等遍历集合时,尝试访问不存在的元素。为了避免该异常,可以在访问前检查是否有下一个元素(如使用`hasNext()`方法),或者使用`Optional`类处理可能为空的情况。正确管理集合边界和条件判断是关键。
55 6
|
1月前
|
Java
Java异常捕捉处理和错误处理
Java异常捕捉处理和错误处理
57 1
|
1月前
|
Java 编译器 开发者
Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面
本文探讨了Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面,帮助开发者提高代码质量和程序的健壮性。
50 2
|
1月前
|
Java
如何在 Java 中处理“Broken Pipe”异常
在Java中处理“Broken Pipe”异常,通常发生在网络通信中,如Socket编程时。该异常表示写入操作的另一端已关闭连接。解决方法包括:检查网络连接、设置超时、使用try-catch捕获异常并进行重试或关闭资源。
92 5
|
1月前
|
存储 安全 Java
如何避免 Java 中的“ArrayStoreException”异常
在Java中,ArrayStoreException异常通常发生在尝试将不兼容的对象存储到泛型数组中时。为了避免这种异常,确保在操作数组时遵循以下几点:1. 使用泛型确保类型安全;2. 避免生类型(raw types)的使用;3. 在添加元素前进行类型检查。通过这些方法,可以有效防止 ArrayStoreException 的发生。
34 3
|
2月前
|
人工智能 Oracle Java
解决 Java 打印日志吞异常堆栈的问题
前几天有同学找我查一个空指针问题,Java 打印日志时,异常堆栈信息被吞了,导致定位不到出问题的地方。
39 2