异常处理、抛出

异常分类

异常是在JDK中定义的一组专门表示各种不正确情况的类。一旦发生了对应的不正确情况,那么JVM就会产生该类的对象(异常对象)。如果,我们的程序没有处理该异常对象,那么这个异常对象会跟随我们程序的调用流程,一层层往上传递,直到被处理掉。如果到了main方法都还没有处理,那么就会结束程序,然后打印异常信息在控制台。

我们通常看到的异常信息,包括了如下几个重要内容:

1、发生在哪个线程中? Exception in thread "main" 由于我们现在是单线程的程序,所以现在所有的异常都发生在thread “main”当中。

2、发生了什么异常? 异常类的类名通常比较长,但是完整的描述了异常的类型。

ArrayIndexOutofBoundsException

NullPointerException

ClassCastException

InputMismatchException

......

3、异常发生的代码位置? 从上往下找第一行自己写的代码。

在JDK的设计中,异常就是一种类。它有庞大的继承结构。最顶级的:Throwable

次级: Exception (异常) Error (错误)

区别: 异常 -- 代码级别可以解决的问题

             错误 -- 系统、环境、硬件等问题 我们需要搞定的是异常。

异常的子类: 异常的子类非常多,除了Runtime Exception(及其子类),其他的都是编译时异常。

编译时异常时指在“编译期”,打红线,告知我们这里有未处理的异常; 但是,编译期打红线的,还有语法错误呀等等。要区分。

牢记:只有指到红线,明确告知我们有未处理的”某某Exception“才是编译时异常。

由于继承的关系,我们都知道最重要的方法都是定义在父类当中的。所以,异常类里面我们最常用的方法是定义在Throwable当中的。 其中最重要的两个方法是:

1、getMessage() --- 该方法是返回这个异常对象中的“详细信息”的。

2、printStackTrace() --- 该方法是打印异常的堆栈信息,使用的是System.err做的打印。

异常的处理

异常处理分两种手段:

1、事前处理 当异常还没有发生的时候,我们根据经验判断这里有可能发生异常,然后书写上提前判断的代码,把异常扼杀,让它根本不会发生。 比如:我们在前面做的,操作数组之前,先判断下标;操作对象之前,先判断非空;操作类型转换之前,先判断instanceof;

这是最好的处理方案,因为这样的话异常根本就不会发生。但是,局限于“经验”和“解决手段”的掌握情况,有些时候我们无法(或暂时无法)提前处理,那么这个时候我们就要使用第2种方案。

2、事后处理 (try - catch - finally) 这个三个关键字合在一起,共同组成了异常处理的代码块,语法如下:

    try{
        正常逻辑下书写的代码,
        但是这个代码有可能发生异常,我们让它试着执行
    
    }catch(要处理的异常类 对象){
        处理异常的代码
    }finally{
        不管是否发生异常都必须要执行的代码
    }
​

首先,三个关键字并不是必须同时使用。try后面可以接catch 或 finally,但是catch 或 finally不能单独出现。所以有三种组合: try - catch | try - finally | try - catch - finally

1、try-catch try块里面放的是正常逻辑的代码,但这段代码有可能发生异常,所以让他在try当中试着运行。 如果try块中的代码没有发生异常,那么程序流程在执行完try当中所有的代码之后,直接跳到catch块的后面,继续顺序往下执行。 如果try块中的代码发生了异常,那么就会停止try块中后续代码的执行,然后JVM拿着这个异常对象,去和catch块中声明的异常变量进行类型匹配。如果匹配上,JVM就认为你捕获住了异常,那么异常就会认为你已经解决了,就算你在catch块中一句代码都不写,它也认为异常被你解决了,不会再发生终止程序,然后带这异常对象向上传播的情况。

而catch块当中,有没有捕获住异常,不是以来于catch块里面的代码,而是依赖于catch后面的"()"当中声明异常变量的类型!只要这个类型与本次发生的异常对象的类型匹配上,那么就捕获住。

当一个try块里面有可能发生多种异常的时候,那么可以书写多个catch块。每个catch块捕获一种异常。 在进行匹配的时候,是按照从上往下的顺序,依次匹配每个catch块。谁匹配上了就进入谁内部。

如果这多个catch块捕获的异常类型没有继承关系,那么它们也没有谁先谁后的书写顺序。但是一旦有继承关系,那么把捕获子类异常的catch块要写在前面,捕获父类异常的catch块要写在后面

2、try-catch-finally 很明显,是在try-catch后面加了一个finally语句块。 finally语句块,书写在最后一个catch块的后面。它里面代码,在执行流程当中,是表示不管是否发生异常,都必须要执行的代码。

finally当中的这个“必须”,是非常强硬的,也就算遇到return语句。他也必须要执行,在代码级别只有"System.exit(0)"可以阻止它。

一些细节

try-catch到底写在哪里?

从“让程序不应为异常而崩溃掉”的角度来讲,那么对于我们来说从main方法开始,可以在每层调用处做try-catch,都可以达到这个效果。

但是,在不同层做try-catch,最终执行的效果是不一样的。 你在哪一层进行了处理,那么就从这一层才开始恢复正常,而它之下的全部被忽略掉了正常执行。

在实际应用当中,try-catch真正的难点不是它的语法,而是它在处理以后,场景仍然是一个流畅的。

JDK7之后的特殊语法

在原生语法当中,如果一个try块有可能发生多种异常,那么我们要书写多个catch块,每个catch块截获一种异常。

try{
​
}catch(异常1){
​
}catch(异常2){
​
}catch(异常3){
​
}

那么JDK7当中,设计了一种新的语法,允许在一个catch块当中,捕获多种异常。

try{
​
}catch(异常1 | 异常2){
​
}

注意点: 1、|符号是分隔异常类型的,而不是分隔两个异常变量;

2、| 符号两边的异常类型不能有继承关系。因为既然取了父类异常,就没有子类异常的事儿了,因为父类引用可以指向子类对象。

finally到底应该写什么代码?

finally是不管是否发生异常,都必须要执行的代码写在它当中。那么什么样的代码才属于这种情况呢?

在现实开发当中,finally当中的代码往往是“资源的清理”,“管道的关闭”,“链接的断开”等等,这种回收清理动作。 以前这种动作在软件公司当中,是要求被严格执行的,需要程序员养成一旦产生了要操作的“管道”、“连接”这种资源的时候,不要先写操作代码,而是要先写finally然后在里面关闭它们,再把鼠标跳到中间书写操作代码。 但是,现在不用了,在JDK8之后有一种新的专门针对于需要关闭资源的异常处理代码块,叫做”try with resource“。

如果finally之前有return语句,那么它到底在什么时候被执行的?

举个栗子:

public int test(){
        int a = 10;
        try {
            return a;
        }finally{
            a = 20;
        }
    }
​
    public static void main(String[] args) {
        int result = new MyClass().test();
        System.out.println(result);
    }
​

在这种情况下result得到的值不是20,而是10。 因为程序一旦执行到return语句,它并不是马上“先”执行finally。而是先把return的所有准备工作完成,包括返回值的准备。这个时候准备好的返回值是10,然后它才再去执行finally,执行完之后直接做流程的返回。所以finally当中对a的改变,没有影响到return之前准备好的返回值。

各种异常如何处理呢?

1、运行时异常,对于一个成熟的Java程序员来说,通常都不是用try-catch处理的。 几乎都是用修改代码或提前判断的方式处理。

数组下标越界,就该去检查为什么会赋值了一个超过最大下标的数; 空指针异常,就该去检查谁为null; 类型转换异常,就该去检查为什么类型不匹配,可不可以做instanceof判断。

2、编译时异常的时候,为了能够通过编译,我们才会大量使用try-catch

异常的抛出

有些方法在实现的过程中,我们会发现可能在执行时出现一些异常情况,很多时候这些异常情况可能是由业务照成的,不是JVM发现的运行异常。 所以,在实际开发中,我们有可能主动抛出一个异常对象,表示出现了业务异常。

语法:throw 异常对象;

由上面这个语法又触发了Java当中异常类设计的初衷。因为异常在Java看来也是有区别的,有些是要求必须在编码的时候进行解决(编译时异常);有些则是在运行的时候报错就可以了,再来修改代码解决。

那么,我们主动throw的异常对象也要遵循这个机制。如果这个异常对象时运行时异常对象(RuntimeException及其子类的对象),那么不用做任何额外的动作,编译直接成功。

但如果我们throw的是一个编译时异常对象,那么也应该在编译期告知调用者,本方法有可能抛出异常,你要处理!所以,这个时候就要在方法申明处配合throws的语法,才能达到编译期警告调用者的效果。

这个效果就是,调用方法完成调用后,会红线报错,报到的是“未处理的异常 某某Exception”。 然后调用者要想解决,那么要么加try-catch;要么继续用throws往上抛。取决于,要解决这个问题的责任者是自己,或自己的再上级调用者。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值