Javase学习16-异常

学习16-异常

1. 异常概述

1.1 什么是异常?有什么用?

当程序执行过程中出现了不正常的情况,这种不正常的情况就叫做异常

如果一门语言在程序出现了异常时,没有提示任何信息,那么这门语言就是失败的语言。

Java语言提供了异常处理机制,在程序出现异常时,jvm将该异常信息打印输出到控制台,供程序员参考。程序员根据异常信息修改程序,增加程序的健壮性

/**
 * @Author TSCCG
 * @Date 2021/6/3 15:32
 */

public class ExceptionDemo01 {
    public static void main(String[] args) {
        int i = 20;
        int j = 0;
        int num = i / j;
        System.out.println(num);


    }
}

运行:

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at ExceptionDemo01.main(ExceptionDemo01.java:10)

1.2 异常的存在形式

在Java中异常是以类和对象的形式存在的,任何一个异常类都可以创建异常对象

/**
 * @Author TSCCG
 * @Date 2021/6/3 15:32
 */

public class ExceptionDemo01 {
    public static void main(String[] args) {
        //创建异常对象
        ArithmeticException ex = new ArithmeticException("发生了异常!");
        System.out.println(ex);
    }
}

结果:

java.lang.ArithmeticException: 发生了异常!

当发生异常时,jvm就会自动创建一个异常对象,然后抛出

public class ExceptionDemo01 {
    public static void main(String[] args) {
        //JVM执行到此处时,检查到异常,创建异常对象: new ArithmeticException("/ by zero");
        //然后JVM将创建的对象抛出,打印输出信息到控制台。
        System.out.println(50/0);

    }
}

2. 异常的分类

  • Object(顶级父类)
    • Throwable(可抛出的)
      • Exception(可处理的)
        • Exception除RuntimeException及其所有子类外的其他所有子类都叫编译时异常。(编译时异常不是在编译阶段发生的异常,而是表示必须在编译程序的时候预先对这种异常进行处理,如果不处理,编译器就报错)
        • RuntimeException及其所有子类都叫运行时异常。(运行时异常在编写程序阶段可以选择处理,也可以选择不处理)
      • Error(不可处理,直接退出JVM)

所有的异常都发生在运行阶段,因为只有当程序运行时才能创建异常对象

异常继承UML图:

01异常继承图

UML图链接:https://www.processon.com/view/60bb6adc07912941e204c79d

脑图:

02异常继承脑图.png

脑图链接:https://www.processon.com/view/60bc1d811e08533a509c0b5f#map

2.1 编译时异常 (checkedException)

Exception除了RuntimeException及其所有子类外,其他所有子类都叫做编译时异常。编译时异常也叫受检异常(CheckedException)或受控异常。
编译时异常不是在编译阶段发生的异常,而是表示必须在编译程序的时候预先对这种异常进行处理,如果不处理,编译器就报错,无法运行。
编译时异常发生的概率较高,就好比出门时外面正在下暴雨。如果不打伞,那么生病的概率就很高,为了避免生病,就要提前准备一把雨伞。

常见的编译时异常:

异常类名 释义
IOException 输入输出流异常
FileNotFoundException 文件找不到的异常
ClassNotFoundException 类找不到异常
DataFormatException 数据格式化异常
NoSuchFileldException 没有匹配的属性异常
NoSuchMethodException 没有匹配的方法异常
SQLException 数据库操作异常
TimeoutException 执行超时异常

2.2 运行时异常 (uncheckedException)

RuntimeException及其所有子类都是运行时异常。运行时异常也叫未受检异常(UnCheckedException)或非受控异常。
运行时异常在编写程序阶段可以选择处理,也可以选择不处理。
运行时异常发生的概率较低,就好比出门时阳光明媚,之后突然下起了暴雨,无法提前预知。你在出门前不知道天气如何,可以提前准备一把雨伞,也可以不准备。

常见的运行时异常:

异常类名 释义
IndexOutOfBoundsException 抛出以表示某种索引(例如数组,字符串或向量)的索引超出范围
ClassCastException 类型转换异常
NullPointerException 空指针异常
IllegalAccessException 非法的参数异常
InputMismatchException 输入不匹配异常
ArithmeticException 抛出异常算术条件时抛出。 例如,“除以零”的整数会抛出此类的一个实例

3. 异常的处理

Java语言中对异常的处理有两种方式:

  1. 使用try...catch语句将异常捕捉。
  2. 在方法声明的位置上,使用throws关键字将异常抛给上一级。谁调用我,我就抛给谁。

举例说明:

我是某饭店的一名服务员,一个失误,导致将菜拍在了顾客的脸上。顾客不满,要求索赔。”顾客要求索赔“可以看作是一个异常发生了。此时,我有两种处理方式:

  1. 我自己掏腰包对顾客进行赔偿。(异常的捕捉)

  2. 将这件事告诉我的上级领导(经理)。(异常上抛)

    我 ----(上抛)----> 经理 ----(上抛)----> 老板

异常发生后,如果我选择将异常上抛给我的调用者(经理),那么我的调用者需要对这个异常继续进行处理,同样有这两种处理方式。

注意:

Java中异常发生后如果一直进行上抛,最终抛给main方法,main方法继续上抛,抛给调用者JVM,JVM知道这个异常发生,只会发生一种结果,那就是终止Java程序的执行。

3.1 try...catch

当程序发生异常时,会立即停止运行,无法继续向下执行。为了保证程序能有效的执行,Java中提供了一种对异常进行处理的方式——异常捕获。

3.1.1 异常捕获try...catch语句基本语法格式

try {
    //可能发生异常的语句
} catch (需要进行捕获的异常类型创建的异常对象) {
    //对捕获的异常进行相应处理
}

例子:

public class ExceptionDemo01 {
    public static void main(String[] args) {
        System.out.println(50/0);
        System.out.println("后面的语句");
    }
}

运行结果:

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at ExceptionDemo01.main(ExceptionDemo01.java:3)

System.out.println("后面的语句");这行代码没有运行

可见当发生异常后如果不去解决就会影响后续语句的执行

使用try...catch对以上代码进行处理:

public class ExceptionDemo01 {
    public static void main(String[] args) {
        /*
          程序先运行try中的语句,如果没有发生异常,程序正常执行,不会执行catch中语句,
          如果发生了异常,会与catch中创建的异常对象进行匹配
          如果匹配,执行catch及后续语句
          如果不匹配,程序终止,不会执行catch及后续语句
         */
        try {
            System.out.println(50/0);
        } catch (ArithmeticException a) {
            //打印异常信息
            a.printStackTrace();
        } 
        System.out.println("后面的语句");

    }
}
java.lang.ArithmeticException: / by zero
	at ExceptionDemo01.main(ExceptionDemo01.java:10)
后面的语句

3.1.2 catch中创建异常对象的类型

catch后面小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类型

import java.io.FileInputStream;
import java.io.FileNotFoundException;

/**
 * @Author: TSCCG
 * @Date: 2021/06/13 17:45
 */
public class ExceptionDemo04 {
    public static void main(String[] args) {
        System.out.println("main start");
        try {
            //创建输入流
            FileInputStream file = new FileInputStream("D:\\study\\实验系统网址密码.TX");
        } catch (IOException e) {//FileNotFoundException的父类,也可以写所有异常的父类Exception
            System.out.println("找不到文件");
        }
        System.out.println("main end");
    }
}

3.1.3 catch可以写多个

catch可以写多个,建议捕获异常的时候,一个一个精确处理,这样有利于程序的调试

第一种方法:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/**
 * @Author: TSCCG
 * @Date: 2021/06/13 17:45
 */
public class ExceptionDemo04 {
    public static void main(String[] args) {
        System.out.println("main start");
        try {
            //创建输入流
            FileInputStream file = new FileInputStream("D:\\study\\实验系统网址密码.TX");
            //读文件
            file.read();
        } catch (FileNotFoundException e) {
            System.out.println("找不到文件");
        } catch (IOException e) {
            System.out.println("读不到文件");
        }
        System.out.println("main end");
    }
}

第二种方法:

jdk8的新特性,可以在catch中以 "异常类型 | 异常类型| 异常类型 变量 "的形式来创建异常对象

import java.io.FileInputStream;
import java.io.FileNotFoundException;

/**
 * @Author: TSCCG
 * @Date: 2021/06/13 17:45
 */
public class ExceptionDemo04 {
    public static void main(String[] args) {
        System.out.println("main start");
        try {
            //创建输入流
            FileInputStream file = new FileInputStream("D:\\study\\实验系统网址密码.TX");
            System.out.println(50 / 0);
        } catch (FileNotFoundException | ArithmeticException | NullPointerException e) {
            System.out.println("找不到文件 or 数字异常 or 空指针异常");
        }
        System.out.println("main end");
    }
}

3.1.4 catch写多个的时候,必须遵守从小到大

catch写多个的时候,创建异常对象的级别,从上到下,必须遵守从小到大的原则

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/**
 * @Author: TSCCG
 * @Date: 2021/06/13 17:45
 */
public class ExceptionDemo04 {
    public static void main(String[] args) {
        System.out.println("main start");
        try {
            //创建输入流
            FileInputStream file = new FileInputStream("D:\\study\\实验系统网址密码.TX");
            //读文件
            file.read();
        } catch (IOException e) {
            System.out.println("读不到文件");
        } catch (FileNotFoundException e) { //报错
            System.out.println("找不到文件");
        }
        System.out.println("main end");
    }
}

IOException是FileNotFoundException的父类,当创建FileInputStream对象出现异常时,第一个catch中的IOException可以对其进行捕获。执行第二个catch时,由于异常已经被捕获,无法再次进行捕获,故编译器报错。

3.1.5 finally

3.1.5.1 语法格式

finally无法单独使用,必须跟在try...catch语句后面。

try {
    //可能发生异常的语句
} catch (IOException e) {
    e.printStackTrace();
} finally {
    
}
3.1.5.2 finally适用场景

通常在finally语句块中完成资源的释放/关闭,因为finally中的代码比较有保障。

例子:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/**
 * @Author: TSCCG
 * @Date: 2021/06/13 21:18
 */
public class ExceptionDemo06 {
    public static void main(String[] args) {
        FileInputStream file = null;
        try {
            //创建输入流对象
            file = new FileInputStream("D:\\study\\实验系统网址密码.TXT");
            //开始读文件。。。
            
            String s = null;
            s.toString();//这里一定会报空指针异常

            //流使用完需要关闭,因为流是占用资源的。如果上面的代码出现异常,那么流就无法关闭,这是非常危险的
            file.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } 
    }
}

结果:

Exception in thread "main" java.lang.NullPointerException
	at ExceptionDemo06.main(ExceptionDemo06.java:17)

使用finally进行处理

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/**
 * @Author: TSCCG
 * @Date: 2021/06/13 21:18
 */
public class ExceptionDemo06 {
    public static void main(String[] args) {
        FileInputStream file = null;
        try {
            //创建输入流对象
            file = new FileInputStream("D:\\study\\实验系统网址密码.TXT");
            //开始读文件。。。

            String s = null;
            s.toString();//这里一定会报空指针异常

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            //不管是否发生异常,finally里的语句都会执行。
            System.out.println("阿巴巴巴");
            try {
                //如果创建流对象失败,那么file在这里仍会是null,会报空指针异常。所以使用if判断避免
                if (file != null ) {
                    //close()方法有异常,采用捕捉的方式
                    file.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

结果:

阿巴巴巴
Exception in thread "main" java.lang.NullPointerException
	at ExceptionDemo06.main(ExceptionDemo06.java:18)
3.1.5.3 finally语句段在程序中的执行

finally语句一定会执行

try和finally,没有catch也可以执行

例子:

public static void main(String[] args) {
    try {
        System.out.println("---try---");
        //结束方法
        return;
    } finally {
        //finally语句即使在try中存在return语句也会执行
        System.out.println("---finally---");
    }
    //以下执行不到,因为在try中执行了return,方法结束
    System.out.println("Hello");
}

结果:

---try---
---finally---

以上代码执行顺序:

先执行---try---

再执行---finally---

最后执行return(return只要执行,方法一定会结束)

3.1.5.4 finally面试题

以下代码输出什么数?

/**
 * @Author: TSCCG
 * @Date: 2021/06/14 09:36
 */
public class ExceptionDemo09 {
    public static void main(String[] args) {
        System.out.println(m1());
    }
    public static int m1() {
        int i = 100;
        try {
            return i;
        }finally {
            i++;
        }
    }
}

结果:

100

java中语法规则:

  • 方法体中的代码必须遵守自上而下的顺序依次逐行执行
  • return语句一旦执行,整个方法必须结束

return i; 语句出现在int i = 100后面,所以最终结果必须是返回100

return语句必须保证是最后执行的,一旦执行,整个方法结束。

反编译class文件:

public static int m1() {
    int i = 100;
    try {
        int n = i;
        return n;
    }
    finally {
        ++i;
    }
}

可见,在try语句中先将i赋给一个临时变量n,然后执行finally语句中的++i,最后将n返回

3.1.5.5 退出JVM finally语句不执行
public static void main(String[] args) {
    try {
        System.out.println("---try---");
        System.exit(0);
    } finally {
        System.out.println("---finally---");
    }
}

结果:

---try---

3.2 throws关键字

一般在程序开发中,开发者通常会意识到程序可能出现的问题,可以直接通过try...catch对异常进行捕获处理。但有些时候,方法中的代码是否会出现异常,开发者并不明确或不急于处理,为此,Java允许将这种异常从当前方法中抛出,然后让后续的调用者在使用时进行异常处理。

格式:

一般将throws关键字写在方法声明的后面,并需要在后面声明方法中发生的异常类型

例子:

main方法将异常继续上抛给JVM

/**
 * @Author: TSCCG
 * @Date: 2021/06/13 11:14
 * throws关键字
 * 用来在方法的声明上去声明抛出异常,多个异常类名中间用逗号分隔
 */
public class ExceptionDemo02 {
    public static void main(String[] args) throws Exception {
        division(50,0);
    }
    public static int division(int a, int b) throws Exception{
        return a / b;
    }

}

结果:

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at ExceptionDemo02.division(ExceptionDemo02.java:10)
	at ExceptionDemo02.main(ExceptionDemo02.java:7)

main方法使用try...catch对异常进行处理

/**
 * @Author: TSCCG
 * @Date: 2021/06/13 11:14
 * throws关键字
 * 用来在方法的声明上去声明抛出异常,多个异常类名中间用逗号分隔
 */
public class ExceptionDemo02 {
    public static void main(String[] args){
        try {
            division(50,0);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("后续语句");
    }
    public static int division(int a, int b) throws Exception{
        return a / b;
    }

}

结果:

java.lang.ArithmeticException: / by zero
	at ExceptionDemo02.division(ExceptionDemo02.java:15)
	at ExceptionDemo02.main(ExceptionDemo02.java:8)
后续语句

可见,throws就是将异常不断地上抛,在将异常上抛后,不会执行后面的语句

4. 异常对象的常用方法

异常对象有两个非常重要的方法:

方法名 作用
getMessage() 获取异常简单的描述信息
printStackTrace() 打印异常追踪的堆栈信息(一般用这个)

实例演示:

import java.io.FileInputStream;
import java.io.FileNotFoundException;

/**
 * @Author: TSCCG
 * @Date: 2021/06/13 20:11
 */
public class ExceptionDemo05 {
    public static void main(String[] args) {
        try {
            m1();
        } catch (FileNotFoundException e) {
            //打印异常信息是必须的,不然很难知道出现的错误
            System.out.println("---------获取异常简单描述信息---------");
            System.out.println(e.getMessage());
            System.out.println("---------打印异常追踪的堆栈信息---------");
            e.printStackTrace();
        }
    }

    private static void m1() throws FileNotFoundException {
        m2();
    }

    private static void m2() throws FileNotFoundException {
        m3();
    }

    private static void m3() throws FileNotFoundException {
        new FileInputStream("D:\\study\\实验系统网址密码.TX");
    }
}

结果:

---------获取异常简单描述信息---------
D:\study\实验系统网址密码.TX (系统找不到指定的文件。)
---------打印异常追踪的堆栈信息---------
java.io.FileNotFoundException: D:\study\实验系统网址密码.TX (系统找不到指定的文件。)
	at java.io.FileInputStream.open0(Native Method)
	at java.io.FileInputStream.open(FileInputStream.java:195)
	at java.io.FileInputStream.<init>(FileInputStream.java:138)
	at java.io.FileInputStream.<init>(FileInputStream.java:93)
	at ExceptionDemo05.m3(ExceptionDemo05.java:29)
	at ExceptionDemo05.m2(ExceptionDemo05.java:25)
	at ExceptionDemo05.m1(ExceptionDemo05.java:21)
	at ExceptionDemo05.main(ExceptionDemo05.java:11)   

如何查看异常信息?

在打印出的异常追踪堆栈信息中,我们只需要看我们自己写的部分即可

如上面打印的结果中,从at ExceptionDemo05.m3(ExceptionDemo05.java:29)这行开始出现我们自己写的东西,那么就从这行开始,从上往下看。

第29行的代码出现异常,导致第25行也出现异常

第25行出现异常,导致21行也异常

第21行异常,导致11行异常

从而可得知,第29行代码是异常的根源,只需要修改第29行代码即可解决异常

5. 自定义异常

SUN公司提供的JDK内置异常是不够用的。在实际开发中,有很多业务,出现异常后,很多JDK中是没有的。这时,我们可以自定义异常类。

5.1 如何自定义异常类

查看异常类源码得知,异常类的结构都是继承一个异常父类,由一个无参构造方法和一个带有String参数的构造方法组成的。

故我们自定义异常的步骤有两步:

  1. 编写一个类继承Exception或RuntimeException
  2. 提供来年规格构造方法,一个无参数,一个带有String参数
public class MyException extends Exception{
    public MyException() {
    }
    public MyException(String s) {
        super(s);
    }
}

调用自定义异常类:

public class ExceptionDemo10 {
    public static void main(String[] args) {
        MyException e = new MyException("异常信息");
        System.out.println(e.getMessage());
        e.printStackTrace();
    }
}

结果:

异常信息
MyException: 异常信息
	at ExceptionDemo10.main(ExceptionDemo10.java:8)
posted @ 2021-06-13 11:27  TSCCG  阅读(70)  评论(0编辑  收藏  举报