天天看点

张小飞的Java之路——第十五章——异常

作者:小艾编程

写在前面:

视频是什么东西,有看文档精彩吗?

视频是什么东西,有看文档速度快吗?

视频是什么东西,有看文档效率高吗?

1. 演示

诸小亮:异常,就是不正常,是在程序运行时出现的各种错误,比如:

public class Demo{
    public static void main(String[] args){
        int num = 2 / 0;//0不能作为除数,所以会产生异常
        System.out.println("程序结束。。。。");
    }
}           

结果:

张小飞的Java之路——第十五章——异常

张小飞:之前也遇到过很多次了,可以具体解释一下这输出都是什么意思吗?

诸小亮:这是必须的,上图中可以分为3个部分:

  • ArithmeticException:是异常类型,
  • / by zero:是异常信息
  • (Demo.java:5) 是异常出现的位置

张小飞:明白了

诸小亮:另外,出现异常后程序会终止,所以 System.out.println("程序结束。。。。"); 不运行

张小飞:噢,你不说还真没注意,我记下了

2. 介绍

诸小亮:在 Java 中,用 Throwable类 对程序运行时的不正常情况进行了描述

张小飞:不对啊,上面的异常不是 ArithmeticException吗?

诸小亮:你说的没错,但是 Java 中所有异常都是 Throwable 的子类,具体分为两大类:

  • Exception:可以通过修改代码进行处理的不正常现象,比如:ArithmeticException
  • Error:一般是系统底层问题,无法听过代码处理,比如:内存溢出
张小飞的Java之路——第十五章——异常

我们主要学习的是如何处理 Exception 这类异常

3. 处理异常

张小飞:那么,如何处理异常呢?

诸小亮:可以使用 try catch 去处理

1. try catch

诸小亮:通过 try catch 代码块,捕获指定的异常并处理,比如:

public static void main(String[] args){
    try {
        int num = 2 / 0;
    }catch (Exception e){//catch 里面捕获指定的异常类型
        
        //TODO 这里书写一些代码处理这种异常情况
        
        //e.getMessage():获取异常信息
        System.out.println("发现错误了。。。。" + e.getMessage());
    }
    System.out.println("程序结束。。。。");
}           

结果:

张小飞的Java之路——第十五章——异常

,程序可以正常结束

张小飞:这句代码 ‘catch (Exception e)’,表示 Exception 的子类型,都可以捕获到吗?

诸小亮:是的,然后就会执行 catch 中的代码

张小飞:您看我这样写行不行

张小飞的Java之路——第十五章——异常

诸小亮:当然没问题,但是这样的话,如果代码中有其他异常,就不能catch到,比如:

public static void main(String[] args){
    try {
        int[] arr = new int[2];
        System.out.println(arr[2]);
        int num = 2 / 0;
    }catch (ArithmeticException e){
        System.out.println("发现算术异常。。。。" + e.getMessage());
    }
    System.out.println("程序结束。。。。");
}           

结果:

张小飞的Java之路——第十五章——异常

解释:因为我们 catch 的是 ArithmeticException ,却出现了ArrayIndexOutOfBoundsException异常,所以并没有走 catch中的代码

张小飞:原来是这样,我明白了

2. throws

诸小亮:下面我们看看——throws关键字

张小飞:这个是做什么用的?

1. 演示

诸小亮;如果不想用 try catch 处理异常,可以通过 throws 关键字声明异常,比如:

//throws 写在方法上,用来声明异常
public static int div(int a, int b) throws Exception{
    return a / b;
}           

张小飞:这是???

诸小亮:就是提醒调用者,这个方法可能发生异常

张小飞:您的意思是,在调用这个方法的时候需要用 try catch?

诸小亮:是的,谁调用谁处理,这时调用这个方法的地方就应该:

public static void main(String[] args){
    try{
        div(2,0);
    }catch (Exception e){
        //这里处理异常
    }

}           

张小飞:如果调用者也不想处理呢?

诸小亮:那就继续 throws,比如:

张小飞的Java之路——第十五章——异常

张小飞:这时候,如果异常,是谁处理?

诸小亮:就是 JVM 自己处理了

2. 多种异常

诸小亮:另外,throws 可以声明多种异常,但是要逗号分隔,比如:

张小飞的Java之路——第十五章——异常

张小飞:这样的话,catch 那里怎么办呢?

诸小亮:如果声明多个异常,catch 也应该有多个,保证每种异常都能被 cath 到并处理,比如:

public static void main(String[] args){
    try{
        div(2,0);
    }catch (ArrayIndexOutOfBoundsException e){

    }catch (ArithmeticException e){
        
    }
}
//throws 写在方法上,用来声明异常
public static int div(int a, int b) throws ArrayIndexOutOfBoundsException, ArithmeticException{
    return a / b;
}           

张小飞:原来如此

诸小亮:还可以优化

张小飞:哦?怎么优化?

诸小亮:因为上面的异常都是 Excepetion 的子类,所以直接 catch 一个 Excetion 就行了,比如:

张小飞的Java之路——第十五章——异常

张小飞:明白了

3. 常用方法

诸小亮:接着我们说说 Exception 中的常用方法

1. getMessage()

诸小亮:getMessage() ,用来获取异常信息,已经演示过,就不在多说了

张小飞:好的

2. toString

诸小亮:toString(),把异常转换为字符串输出,你可以尝试一下

张小飞:我试过了,您看看

张小飞的Java之路——第十五章——异常

结果:

张小飞的Java之路——第十五章——异常

3. printStackTrace

诸小亮:printStackTrace(),打印异常的堆栈信息,这个功能比较有用

张小飞:是这样吗?比如:

张小飞的Java之路——第十五章——异常

结果:

张小飞的Java之路——第十五章——异常

诸小亮:没错,上图中,展示出了具体报错的位置

4. throw关键字

诸小亮:接下来,我们看看——throw关键字

张小飞:咦,这个不是刚才说过来吗?

诸小亮:看清楚,刚才说的是 throws,现在是 throw,不一样

张小飞:哦哦,这个 throw 又是什么意思?

诸小亮:throw,用来抛出指定的异常,比如:

public static void main(String[] args){
    try {
        int num = div(2,0);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
public static int div(int a, int b) throws Exception{
    if(b == 0){
        //通过 throw 关键字,抛出指定的异常,
        //当程序运行到这里后中断,下面的 return a/b; 不再运行
        throw new ArithmeticException("小学没毕业吗?除数不能是0---"+b);
    }
    return a / b;
}           

结果:

张小飞的Java之路——第十五章——异常

张小飞:原来还可以这样,不过这有什么用呢?不用 throw 也会自动抛出异常啊?

张小飞的Java之路——第十五章——异常

诸小亮:这样做,有两个好处,第一,可以自定义异常信息

张小飞:噢,确实,我们自定义的信息更加清楚一些,第二呢?

诸小亮:第二,可以抛出其他异常,比如:

张小飞的Java之路——第十五章——异常

张小飞:明白了

诸小亮:另外,如果不知道应该抛出什么异常,可以使用Exception,比如:

张小飞的Java之路——第十五章——异常

张小飞:我试了试,抛出这个是有问题的,您看看

张小飞的Java之路——第十五章——异常

诸小亮:因为,只有 Throwable 类型的对象才能被 throw 抛出

张小飞:原来如此

诸小亮:来,我们总结一下 throw 跟 throws 的区别

  • 位置不同
    • throws:用在方法上,其后指定异常的种类可以有多个
    • throw:用在方法内,其后指定具体的异常对象
  • 功能不同
    • throws:声明异常,告诉调用者可能会出现问题
    • throw:抛出异常,程序运行到此处时就中断了

张小飞:明白了,不过,一般什么时候用 throw 呢?

诸小亮:一般用来校验参数,如果参数不对抛出异常

5. RuntimeException

诸小亮:下面我们说说——RuntimeException

张小飞:这个是什么意思?

“RuntimeException,运行时异常,即:程序运行时抛出的异常,这是 Exception 下比较特殊的一个儿子”

“哦?怎么个特殊法?”

“一般情况下,只是在代码中用 throw 抛出异常后,会编译失败,比如:”

张小飞的Java之路——第十五章——异常

“这时候需要在方法上使用 throws 声明异常,比如:”

张小飞的Java之路——第十五章——异常

张小飞:不对吧,刚才我没有用 throws ,也没有编译失败啊

诸小亮:因为 IllegalArgumentException 是 RuntimeException 的子类

  • 使用 throw 抛出运行时异常,不需要在方法上声明
张小飞的Java之路——第十五章——异常

张小飞:原来如此

张小飞:那,ArithmeticException ArrayIndexOutOfBoundsException,也是运行时异常?

诸小亮:是的

张小飞:为什么这样呢?使用 throws 后,调用者就需要用 try catch 处理,这样不是更好吗?

如果不声明的话,调用者就可能忘记使用 try catch,这样程序容易报错啊

诸小亮:你说的没错,不过出现RuntimeException,一般都是调用者写的代码有问题

这样做的目的:就是停止程序,让调用者修改代码

张小飞:原来是这样

诸小亮:因为 RuntimeException,所以可以把 Exception 分为两类——受检 和 非受检异常

张小飞:这是什么意思?

诸小亮:给你解释一下

  • 受检异常:在编译的时,要强制检查的异常,这种异常需要去通过 try catch 来进行捕获或者使用throws在方法上声明,否则无法通过编译的
  • 非受检异常:在编译的时,不需要去强制检查的异常,这种异常可以不需要显示去捕获或者抛出

张小飞:那么,RuntimeException 就是非受检异常?

诸小亮:没错,在 Exception 下,RuntimeException就是非受检异常,其他的都是受检异常

张小飞:明白了

6. 自定义异常

张小飞:自定义异常——是自己定义一个类,来描述某种异常吗?

诸小亮:是的

张小飞:Java已经提供 Exception,为什么还要自定义异常呢?

诸小亮:Java 虽然用 Exception 描述程序运行时的不正常情况,但是现实中的异常多种多样

其内部的异常类型(Exception的子类)可能不满足实际需求,这时候就要自定义异常

1. 演示

诸小亮:比如,定义一个方法做除法运算,如果除数小于等于0,就抛出一个除数是负数的异常

目前 Java 中没有定义除数是负数的异常,这时候就需要自定义

示例:

//自定义异常,注意:一般使用自定义异常都是起一些容易理解的名字,这样阅读性更好
class FuShuException extends Exception{

}

public class Demo{
    public static void main(String[] args) {
        
        try {
            //除数传一个负数
        	int num = div(2,-1);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static int div(int a, int b) throws FuShuException {
        if(b < 0){
            throw new FuShuException();
        }

        if(b == 0){
            throw new ArithmeticException("除数不能等于0。。。。。");
        }
        return a / b;
    }
}           

张小飞:没有必要自定义吧,这样子岂不是更好,您看看

张小飞的Java之路——第十五章——异常

诸小亮:你说的不错,但是有时候需要针对不同的异常,做不同的处理,比如:

张小飞的Java之路——第十五章——异常

张小飞:明白了

2. 自定义异常信息

张小飞:您定义的这个 FuShuException ,不能自定义异常信息啊,IllegalArgumentException是可以的

他们不都是 Exception 的子类吗?

诸小亮:这时因为,FuShuException少一个构造函数,比如:

FuShuException(String message){
    super(message);//调用 super,设置异常信息
}           

张小飞:原来如此,我来试试

3. 继承RuntimeException

诸小亮:另外,目前 FuShuException 继承 Exception,必须在方法上用 throws 声明。。。。

张小飞:这个简单,直接继承 RuntimeException 就行了,比如:

张小飞的Java之路——第十五章——异常
张小飞的Java之路——第十五章——异常

诸小亮:看来你已经充分了解:非受检异常

7. finally

诸小亮:接着来看看——finally代码块

张小飞:这又是干什么用的?

1. 演示

诸小亮:finally 是配合 try catch 使用的一种特殊的代码块,比如:

public static void main(String[] args)  {
    try{
        int num = 2 /0 ;
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        System.out.println("finally中的代码执行。。。。");
    }
}           

结果:

张小飞的Java之路——第十五章——异常

诸小亮:你有没有什么发现?

张小飞:finally 中的代码,在 catch 处理异常后,正常执行

诸小亮:说得对,这就是它最重要的地方

2. finally中的代码一定会执行

诸小亮:其实,finally代码块最重要的意义就是:它里面的代码一定会执行

张小飞:一定会执行?

“没错,即使在 catch 中使用 return 语句,finally也会执行,比如:”

public static void main(String[] args)  {
    try{
        int num = 2 /0 ;
    }catch (Exception e){
        e.printStackTrace();
        return;//catch中加上return语句,finally中的代码依然执行
    }finally {
        System.out.println("finally中的代码执行。。。。");
    }
}           

结果:

张小飞的Java之路——第十五章——异常

“有没有什么例外情况?”

“凡事都有例外,如果是退出 JVM 虚拟机,finally不执行,比如:”

张小飞的Java之路——第十五章——异常

结果:

张小飞的Java之路——第十五章——异常

3. return 和 finally

诸小亮:问你个问题,return后的语句和finally中的语句,哪个先执行?

张小飞:额....,稍等我验证一下

public static void main(String[] args)  {
    show();
}

private static int show() {
    try{
        int num = 2 /0 ;
    }catch (Exception e){
        e.printStackTrace();
        //return 后面调用一个方法
        return test();
    }finally {
        System.out.println("finally中的代码执行。。。。");
    }
    return 1;
}

private static int test() {
    System.out.println("返回一个0.....");
    return 0;
}           

结果:

张小飞的Java之路——第十五章——异常

张小飞:这跟我预想的结果不太一样,本来以为 finally 中的代码先执行呢?

诸小亮:它的执行过程是这样的:

  • 先执行 test 方法,拿到一个返回值 0
  • 再执行 finally 中的代码
  • 最后执行 return,把 0 给返回

张小飞:明白了

8. try catch finally 的组合方式

诸小亮:下面给你介绍几种 try catch finally 常用的组合方式

“第一种:try catch 单独使用,这种最常见”

张小飞的Java之路——第十五章——异常

“第二种:一个try,多个catch”

张小飞的Java之路——第十五章——异常

“第三种:try 和 finally,没有catch”

张小飞的Java之路——第十五章——异常

张小飞:“这样也行?产生的异常时被谁处理了?”

诸小亮:“因为没有catch,所以会自动把异常抛给调用者,但是 finally 中的代码一定会执行”

9. 方法覆盖时的异常

诸小亮:在做继承的时候,有几点需要注意

第一:如果父类方法或父接口没有 throws 异常,那么子类复写的方法也不能 throws 异常

class HeroException extends Exception{
}

class YaseException extends HeroException{
}

class DajiException extends Exception{
}

interface People{
    void run();
}
class Person{
    void eat(){
    }
}
class child extends Person implements People{
    //复写父类的方法
    @Override
    void eat()throws HeroException {
        System.out.println("吃饭。。。。。");
    }

    //实现父接口的方法
    @Override
    public void run()throws HeroException {
        System.out.println("走。。。。。");
    }
}           

张小飞:上面的代码,编译失败了

张小飞的Java之路——第十五章——异常

第二:子类方法声明的异常类型,只能是父类声明的异常类型或者其子类型,比如:

张小飞的Java之路——第十五章——异常

张小飞:您的意思是,子类复写后 run 方法后,声明的异常只能是 HeroException 或 其子类型?

诸小亮:是的,准确来说有 3 种方式可以选择

第一种:声明 HeroException

张小飞的Java之路——第十五章——异常

第二种:声明 HeroException 的子类型

张小飞的Java之路——第十五章——异常

第三种:不声明异常

张小飞的Java之路——第十五章——异常

张小飞:如果是声明其他类型的异常呢?

诸小亮:那么会直接编译失败,比如:

张小飞的Java之路——第十五章——异常

张小飞:如果父类方法没声明异常呢?

诸小亮:这是最特殊的一种情况,子类复写方法后,可以声明 RuntimeException 或 Error ,比如:

张小飞的Java之路——第十五章——异常

继续阅读