异常的分类

Throwable 是 Java 语言中所有错误与异常的**超类**
Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。
Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了printStackTrace()等接口用于获取堆栈跟踪数据等信息。

Error(错误)

定义:**Error**类及其子类。程序中无法处理的错误,表示运行应用程序中出现了严重的**错误**
特点: 此类错误一般表示代码运行时 **JVM **出现问题。通常有 Virtual MachineError(虚拟机运行 错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。
这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错 误。按照Java惯例,我们是不应该实现任何新的Error子类的!


Exception(异常)

Exception是一种异常。就如我们常见的**空指针异常**或者是**1除以0**的异常。主要是由于我们自身的代码引起的问题。
程序本身可以捕获并且可以处理的异常。Exception这种异常又分为两类:CheckedException(编译时异常)和 RuntimeException(运行时异常)

CheckedException

检查时异常:

ClassNotFoundException 类找不到
IOException IO流异常
ClassNotFoundException
NotSerializableException 序列化异常
parseException 格式化异常

Java 编译器会检查它。如果程序中出现此类异常,比如 ClassNotFoundException(没有找到指定的类异常),IOException(IO流异常),要么通过**throws**进行声明抛出,要么通过**trycatch**进行捕获处理,否则不能通过编译。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。该异常我们必须手动在代码里添加捕获语句来处理该异常。

必须要求我们在代码里进行处理,如果我们不处理CheckedException的话,我们的应用程序是**无法通过编译阶段**的。

  1. public class TestException {
  2. public void test1() {
  3. System.out.println("test exception");
  4. throw new Exception("this is a error");
  5. }
  6. }

就如上面代码,如果我们不在test1()方法上显示抛出throws Exception(将异常抛给调用方进行处理,不在本方法来处理),那么IDEA就会给我提示红色警告。这个就是不处理CheckedException的结果。或者我们不将异常直接抛出去,而是选择在test1()方法内进行try...catch捕捉异常,这时候我们有两种方式来处理我们捕捉到的异常:

  1. 选择记录日志的方式处理异常问题
  2. 将异常信息直接返回回去

RuntimeException

运行时异常

NullPointerException 空指针异常
ClassCaseException 类转换异常
IndexOutOfBoundsException 数组越界
ArrithmeticException 除0异常
MissingResourceException 缺少资源错误
IllegalArgumentException 非法参数异常
ArrayIndexOutBoundException 数组下标越界异常
ArithmeticExecption 算术异常

Java 编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既”没有通过 **throws**声明抛出它”,也”没有用**try-catch**语句捕获它”,还是会编译通过。
一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。虽然 Java 编译器不会检查运行时异常,但是我们也可以通过 throws 进行声明抛出,也可以通过 try-catch 对它进行 捕获处理。如果产生运行时异常,则需要通过修改代码来进行避免。例如,若会发生除数为零的情 况,则需要通过代码避免该情况的发生!

  1. public class TestException {
  2. public void test1() {
  3. System.out.println("test exception");
  4. throw new RuntimeException("this is a error");
  5. }
  6. }

将之前的代码throw new Exception("this is a error");改为throw new RuntimeException("this is a error");。我们会发现即使我们不在test1()方法上使用throws Exception或者没有在test1()方法内进行try...catch捕捉异常,IDEA也不会给我们进行报错。甚至它还会提示throws是多余的。所以RuntimeException是不强制我们处理异常的。

总结

**CheckedException**算是一种分类来着吧,其实是Exception. 检查型异常
**RuntimeException**是有一个实际类,它继承自Exception. 运行时异常

  1. 检查型异常
    这种类型的异常是难以避免的,所以是必须要进行**tr...catch**进行处理的。编译器会帮助我们进行检查,一旦我们没有进行处理(throws或者try…catch)。编译器便会在编译前给我们提示报错。需要进行处理才能通过编译。
  2. 运行时异常
    这种类型的异常是可以避免的。既然是可以避免的话,那我们就可以不用进行处理。但是,有时候我们是很难发现这类异常,不能将它避免开的。可编译器以为我们能避免这异常,它就不会帮助我们进行检查,所以也不会在编译阶段提示报错。但是,我们代码运行至此时,这时候异常就发生了。那就GG了…

编译时异常和运行时异常的区别

  • 编译时异常一般发生的概率比较高。
  • 运行时异常一般发生的概率比较低。
  • 对于一些发生概率较高的异常,需要在运行之前对其进行预处理。
  • 假设Java中没有对异常进行划分,没有分为:编译时异常和运行时异常,所有的异常都需要在编写程序阶段对其进行预处理,将是怎么样的效果呢?
    • 如果这样的话,程序肯定是绝对安全,但是编写程序太累,到处都是处理异常的代码。
    • 假设出门之前,把能够发生的异常都预先处理,那么会更加安全,但活得很累。

Java异常关键字

  • try (用于监听)。将要被监听的代码(可能抛出异常的代码)放在**try**语句块之内,当try语句块内发生异常时,异常就被抛出。
  • catch (用于捕获异常)用于捕获异常。catch用来捕获try语句块中发生的异常。
  • finally (finally语句块总是会被执行)它主要用于回收在try块里打开的物力资源(如数据库连接、 网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或 者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
  • throw (用于抛出异常)
  • throws – 用在方法签名中,用于声明该方法可能抛出的异常。

异常处理

第13章:异常处理 - 图1

捕获处理

对于检查性异常,通过 try catch finally 关键字 实现

  1. try{ }

尝试捕获,把可能出现异常的代码放入其中 [ 必须的 ]

  1. catch( 异常类型 e ){ }

捕获到异常,在这里可以做异常的修复处理[ 可以有多个,如果是运行异常可以没有,那也就处理不了异常了]

  1. finally{ }

始终执行的语句块,无论是否真的触发异常它都要执行。[ 可以选 ]

细节:

  1. 如果出现多个catch 必须 子异常再前 父异常在后
  2. 如果出现多个catch 也可简化成 使用一个最大异常来捕获

示例

  1. public class TestException {
  2. public static void main(String[] args) {
  3. // 1. 检查异常
  4. System.out.println("开始");
  5. String dateStr = "2008-08-ab";
  6. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  7. try {
  8. Date date = sdf.parse(dateStr);
  9. System.out.println("--呵呵--");
  10. } catch (ParseException e) {
  11. e.printStackTrace();//打印异常信息
  12. }finally {
  13. System.out.println("异常处理完成");
  14. }
  15. System.out.println("结束");
  16. // 2. 运算时异常
  17. System.out.println("BEIGN");
  18. int[] data = {1,2,3};
  19. try {
  20. int x = data[10];
  21. System.out.println(x);
  22. }catch (ArrayIndexOutOfBoundsException e){
  23. e.printStackTrace();
  24. }finally {
  25. System.out.println("处理完成");
  26. }
  27. System.out.println("OVER");
  28. }
  29. }

积极处理

2.2 异常抛出

声明异常抛出的方式处理异常,利用一个 throws 关键字 。在方法的签名出添加 异常声明。
Java 中异常发生之后,如果一直上抛,最终抛给了main方法,main方法继续往上抛,抛给了调用者JVMJVM知道这个异常发生,只有一个结果「终止Java程序的执行」

细节 : 声明的异常必须与待处理异常类型相同或更大。如果内部待处理异常有多个,也可以声明多个,有时候为了简洁也可以抛出最大的异常比如Exception。 子类重写父类或者实现类重写接口中的方法,必须不能抛出比上级更大的异常。

语法 : …. xxxx() throws 异常类名1,异常类名N

  1. public class TestException {
  2. public static void main(String[] args) {
  3. String xx = "2009-11-11";
  4. Date date = null;
  5. try {
  6. //这里调用自己的方法需要处理,如果再抛出,到了jvm ,直接报错
  7. date = string2Date(xx);
  8. } catch (ParseException e) {
  9. e.printStackTrace();
  10. }
  11. System.out.println(date);
  12. }
  13. //封装一个转型工具 ,内部存在检查异常,在方法签名出声明抛出,由调用则处理
  14. public static Date string2Date(String str) throws ParseException {
  15. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  16. Date date = sdf.parse(str);
  17. return date;
  18. }
  19. }

自定义异常

理解自定义异常

Java 提供了不同的异常类,不同异常类是为了描述不同的意外情况。在实际开发中,有一些业务上的意外情况,没有现有的异常类来描述。比如 转账,转账金额超了余额,可以使用一个比如 MoneyTooLessException。自定义异常一般都是服务于业务逻辑的异常。一旦违背了业务规范则抛出自定义异常。

体会: 使用自定义异常体现了 规范性 以及可以实现代码解耦合( 异常的产生 和处理分开 )

实现自定义异常

自己编写一个类 去继承 Exception , 意味着我们这个类是一个检查异常。如果继承RuntimeException 就意味着这类是一个运行时异常。

  1. /**
  2. * 自定义异常:描述余额不足
  3. */
  4. public class MoneyTooLessException extends Exception {
  5. public MoneyTooLessException() {
  6. }
  7. public MoneyTooLessException(String message) {
  8. super(message);
  9. }
  10. }

使用自定义异常

在达到了必须抛出此类异常的地方,创建出该异常类对象通过 throw 异常对象;方式抛出即可

  1. public class TestMyException {
  2. public static void main(String[] args) {
  3. transMoney(10000);
  4. }
  5. public static void transMoney(int money) {
  6. //余额--- 从数据库查
  7. int total = 1000;
  8. if( money > total ){
  9. throw new MoneyTooLessException2("余额不足");//手动抛出异常
  10. }else{
  11. System.out.println("转账成功!");
  12. }
  13. }
  14. }

异常对象的常用方法

getMessage

getMessage();方法获取异常发生后的简单描述信息,这个信息实际上是构造方法上String参数。

  1. try {
  2. System.out.println(10 / 0);
  3. } catch(ArithmeticException e) {
  4. String errorMessage = e.getMessage();
  5. System.out.println(errorMessage);
  6. }

/ by zero

  1. ArithmeticException e = new ArithmeticException("哟!傻子又在找BUG呢?");
  2. System.out.println(e.getMessage());

哟!傻子又在找BUG呢?

printStackTrace

printStackTrace 用于打印异常堆栈信息。java 后台打印异常堆栈追踪信息的时候,采用了异步线程的方式打印的。

  1. try {
  2. System.out.println(10 / 0);
  3. } catch(ArithmeticException e) {
  4. e.printStackTrace();
  5. }

java.lang.ArithmeticException: / by zero at com.upyou.learns.ExceptionTest05.main(ExceptionTest05.java:6)

  1. ArithmeticException e = new ArithmeticException("哟!傻子又在找BUG呢?");
  2. e.printStackTrace();

java.lang.ArithmeticException: 哟!傻子又在找BUG呢? at com.upyou.learns.ExceptionTest05.main(ExceptionTest05.java:6)

JDK8新特性

jdk8的特性,可以使用的方式捕捉多个异常

  1. public static void main(String[] args) {
  2. try {
  3. readFile("***");
  4. System.out.println(10 / 0);
  5. }catch(FileNotFoundException | ArithmeticException e){
  6. e.printStackTrace();
  7. System.out.println("哦豁!又是一个找不到异常的傻子!");
  8. }
  9. }
  10. public static void readFile(String filePath) throws FileNotFoundException {
  11. FileInputStream file = new FileInputStream(filePath);
  12. }

查找异常信息

找一个并不存在的文件,肯定会报错,那么我们假装不知道这个文件存不存在!!!如何查看异常的追踪信息呢!

  1. public static void main(String[] args) {
  2. try {
  3. M1();
  4. } catch (FileNotFoundException e) {
  5. e.printStackTrace();
  6. }
  7. }
  8. private static void M1() throws FileNotFoundException {
  9. FileInputStream FIS = new FileInputStream("../哈哈哈哈懵了吧!你找不到我.dog");
  10. }

以上程序报错信息如下:

java.io.FileNotFoundException: ../哈哈哈哈懵了吧!你找不到我.dog (No such file or directory)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.(FileInputStream.java:138)
at java.io.FileInputStream.(FileInputStream.java:93)
at com.upyou.learns.ExceptionTest05.M1(ExceptionTest05.java:20)
at com.upyou.learns.ExceptionTest05.main(ExceptionTest05.java:9)

  1. 异常追踪信息应该从上往下看,首先看异常描述信息
  2. 需要注意的是,只需要看自己写的代码异常信息就可以了,SUN写的代码就别看了,肯定是你自己写的BUG。那么如何得知是否是SUN写的?
    • 大多数报错信息前几行都是Java的代码,例如:
      image.png
      一般看到什么java.xxx.xxx的直接略过,我们写的代码一般都写到自己的工作空间内,例如com.upyou.learnscom.upyou.xxx
    • 在我们写的代码的异常中,查看第一个异常位置,通常都是一个方法的异常引起的连锁反应。
      20行出现问题导致第9行出现问题
    • 第一个异常位置at com.upyou.learns.ExceptionTest05.M1(ExceptionTest05.java:20),错误在com.upyou.learns包下的ExceptionTest05类里的M1方法,在第20行java代码。
    • 找到啦!原来是没有这个.dog文件 🤪🤪🤪🐶🐶🐶

finally语句

tryfinally,没有catch可以吗?可以。

try不能单独使用,可以和finally连用。

try...finally是先执行try再执行finally,最后执行return语句,无论return在哪「return只要执行,方法必然结束」。finally语句块一定会执行

  1. try {
  2. System.out.println("try...");
  3. return;
  4. } finally {
  5. System.out.println("finally...");
  6. }

try...

finally...

退出JVM后,finally就无法执行了

  1. try {
  2. System.out.println("try...");
  3. System.exit(0);
  4. } finally {
  5. System.out.println("finally...");
  6. }

try...

有这么一个程序:

  1. public static void main(String[] args) {
  2. System.out.println(m());
  3. }
  4. public static int m() {
  5. int i = 100;
  6. try {
  7. return i;
  8. } finally {
  9. i++;
  10. }
  11. }

100

Java语法规则(有一些规则是不能破坏的,一旦这么说就不能变):

方法体中的代码必须遵循自上而下顺序依次逐行执行

以上程序finally确实是先于return执行。

以上程序反编译之后的结果
image.png

从反编译结果来看,Java遵循了自己定的语法规则「return必须最后执行、自上而下顺序执行」

final、finally、finalize

final是一个关键字,表示最终的,不变的。第一次赋值之后不可第二次赋值。

  1. final int i = 100;
  2. // i = 200; // 错误

finally也是一个关键字,和try联合使用,使用在异常处理机制中,在finally语句快中的代码是一定会执行的。

  1. try{
  2. ...
  3. } finally {
  4. ...
  5. }

finalizeObject类中的一个方法,作为方法名出现。所以finalize是标识符。这个方法是由垃圾回收器负责调用。

自定义异常

Java中如何自定义异常?

  1. 编写一个类继承Exception或者RuntimeException
  2. 提供两个构造方法,一个无参数一个带有String参数的构造方法。

自定义编译时异常

定义一个编译时异常

  1. public class NotFoundMeException
  2. extends Exception {
  3. public NotFoundMeException(){super();}
  4. public NotFoundMeException(String errorMessage){
  5. super(errorMessage);
  6. }
  7. }

如何使用这个异常对象捏?

new一个异常对象并且使用throw抛出,throws上抛给调用者

  1. public static void test(String str) throws NotFoundMeException{
  2. if (str.isEmpty()) {
  3. throw new NotFoundMeException("hiahiahia!我是异常,你找不到我!!");
  4. }
  5. }

处理这个异常

  1. public static void main(String[] args) {
  2. try {
  3. test("");
  4. } catch(NotFoundMeException e) {
  5. e.printStackTrace();
  6. }
  7. }

com.upyou.learns.MyException.NotFoundMeException: hiahiahia!我是异常,你找不到我!!
at com.upyou.learns.ExceptionTest07.test(ExceptionTest07.java:18)
at com.upyou.learns.ExceptionTest07.main(ExceptionTest07.java:10)

自定义运行时异常

定义一个运行时异常类,继承于RuntimeException

  1. public class NotFoundMeException02
  2. extends RuntimeException{
  3. public NotFoundMeException02(){super();}
  4. public NotFoundMeException02(String errorMessage){super(errorMessage);}
  5. }

使用这个异常类

  1. public static void runTest(String str)
  2. throws NotFoundMeException02{
  3. if (str.isEmpty()) {
  4. throw new NotFoundMeException02("哟!大家快看这个傻子又在写BUG!");
  5. }
  6. }
  1. runTest("");

Exception in thread “main” com.upyou.learns.MyException.NotFoundMeException02: 哟!大家快看这个傻子又在写BUG!
at com.upyou.learns.ExceptionTest07.runTest(ExceptionTest07.java:23)
at com.upyou.learns.ExceptionTest07.main(ExceptionTest07.java:10)

👉练习源码:JavaExceptionLearn-fc943937125c4893b85b66c793197763.zip👈

不建议在main方法上使用throws,因为这个异常如果真正发生了,一定会抛给JVMJVM只有终止。 重写之后的方法不能比重写之前的方法抛出更多(更宽泛)的异常,可以更少。

if-else 解决异常

  1. public class Test01 {
  2. public static void main(String[] args) {
  3. //实现一个功能:键盘录入两个数的商
  4. Scanner sc = new Scanner(System.in);
  5. System.out.println("请录入第一个数");
  6. if(sc.hasNextInt()){
  7. int num1 = sc.nextInt();
  8. System.out.println("请输入第二个数");
  9. if (sc.hasNext()){
  10. int num2 = sc.nextInt();
  11. if(num2==0){
  12. System.out.println("对不起,除数不能为0");
  13. }else
  14. System.out.println("两个数的商:"+num1/num2);
  15. }else {
  16. System.out.println("对不起,你录入的不是int类型的数据");
  17. }
  18. }else {
  19. System.out.println("对不起,你录入的不是int类型的数据");
  20. }
  21. }}

用if-else堵漏洞的缺点
1、代码臃肿,业务代码和处理异常的代码混在一起
2、可读性差
3、程序员需要花费大量的经理来维护这个漏洞
4、程序员很难堵住所有的漏洞

try-catch

  1. public class Test02 {
  2. public static void main(String[] args) {
  3. //实现一个功能:键盘录入两个数的商
  4. try{
  5. Scanner sc = new Scanner(System.in);
  6. System.out.println("请录入第一个数");
  7. int num1 = sc.nextInt();
  8. System.out.println("请输入第二个数");
  9. int num2 = sc.nextInt();
  10. System.out.println("两个数的商:"+num1/num2);
  11. }catch (Exception ex){
  12. System.out.println("对不起,程序出现异常");
  13. }
  14. }}