异常处理机制

目录

1、什么是异常,java提供异常处理机制有什么用?

2、java语言中异常存在形式

3、异常的继承结构

3.1 异常结构图

3.2 异常发生时机

3.3 编译时异常与运行时异常区别

4、异常处理的两种方式

5、出现异常后代码哪里执行哪里不执行

6、try...catch语法

7、throws和try...catch的选择

8、异常对象的常用方法

9、异常追踪信息查看技巧

10、关于try..catch中的finally子句

11、finally执行顺序的一个面试题

 12、final finally finalize 的区别

13、自定义异常

13.1 自定义异常方法

13.2 自定义异常在开发中的应用

14、关于方法覆盖中子类不能抛出比父类更多的异常


1、什么是异常,java提供异常处理机制有什么用?

什么是异常:程序执行过程中的不正常情况。

异常的作用:增强程序的健壮性。(观察到异常后对程序进行修改增加健壮性)

以下程序执行过程中发生了不正常的情况,而这种不正常的情况叫做:异常

java语言是很完善的语言,提供了异常的处理方式,以下程序执行过程中出现了不正常情况,

java把该异常信息打印输出到控制台,供程序员参考。程序员看到异常信息之后,可以对程序进行修改,让程序更加的健壮。

该异常信息时jvm创建、打印的

观察后修改

2、java语言中异常存在形式

异常在java中以类的形式存在,每一个异常类都可以创建异常对象

3、异常的继承结构

3.1 异常结构图

        Object
        Object下有Throwable(可抛出的)
        Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)
                                                  Exception下有两个分支:
            Exception的直接子类:编译时异常(要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错,因此得名编译时异常。)。

如:ClassNotFoundException编译时异常

(方法抛出异常交给调用者处理,调用者没有处理,编译器报错)

 RuntimeException:运行时异常。(在编写程序阶段程序员可以预先处理,也可以不管,都行。)

异常机构图:

3.2 异常发生时机

编译时异常和运行时异常,都是发生在运行阶段。编译阶段异常是不会发生的。

再次强调:所有异常都是发生在运行阶段的

编译时异常因为什么而得名?

因为编译时异常必须在编译(编写)阶段预先处理,如果不处理编译器报错,因此得名

所有异常都是在运行阶段发生的。因为只有程序运行阶段才可以new对象;因为异常的发生就是new异常对象

3.3 编译时异常与运行时异常区别

编译时异常一般发生的概率比较高

(对于一些发生概率较高的异常,需要在运行之前对其进行预处理)

运行时异常一般发生的概率比较低

(没必要提前对这种发生概率较低的异常进行预处理)

假设java中没有对异常进行划分,没有分为:编译时异常和运行时异常,所有的异常都需要在编写

程序阶段对其进行预处理,首先,如果这样的话,程序肯定是绝对的安全的。但是程序员编写程序

太累,代码到处都是处理异常的代码。

4、异常处理的两种方式

第一种方式:在方法声明的位置上,使用throws关键字,抛给上一级。可以抛出多个异常,用逗号隔开

谁调用我,我就抛给谁。抛给上一级。

第二种方式:使用try..catch语句进行异常的捕捉

这件事发生了,谁也不知道,因为我给抓住了

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

例子:

继续抛

try...catch(自己处理,调用者不知道)

注意:一般不在main方法上throws;因为这个异常如果真正的发生了,一定会抛给JVM。JVM只有终止异常处理机制的作用就是增强程序的健壮性。怎么能做到,异常发生了也不影响程序的执行。所以一般main方法中的异常建议使用try...catch进行捕捉。main就不要继续上抛了。

注意:Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果。终止java程序的执行

例如:

这里的HelloWorld没有输出,没有执行 

程序执行到此处发生了ArithmeticException异常,底层new了一个ArithmeticException异常对象,
然后抛出了,由于是main方法调用了100 / 0,所以这个异常ArithmeticException抛给了main方法,main方法没有处理,将这个异常自动抛给了JVM。JVM最终终止程序的执行。

ArithmeticException 继承 RuntimeException,属于运行时异常。在编写程序阶段不需要对这种异常进行预先的处理

5、出现异常后代码哪里执行哪里不执行

(1)对于出异常的方法这里:

        首先当对象调用方法(构造方法也是方法)出现异常后,该方法(底层)会new异常对象(一般是编译时异常),并且throw抛出,再用throws抛给调用者;注意只要throw了这个方法中断执行

(2)对出调用者这里:

        出现异常后,只要异常没有捕捉,采用上报(抛出)的方式,此方法后面代码不会执行。

另外需要意,try语句块中的某一行出现异常,该行后面的代码不会执行。try..catch捕捉异常之后,后续代码可以执行

(new FileInputStream("");这里路径错误,底层new了异常对象)

6、try...catch语法

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

(2)catch可以写多个,也可以写一个大的概括所有。建议catch的时候,精确的一个一个处理。这样有利于程序的调试 

(3)catch写多个的时候,从上到下,必须遵守从小到大

(4)JDK8的新特性,一个catch里面可以写多个异常类型,用"|"隔开

7、throws和try...catch的选择

如果希望调用者来处理,选择throws上报。其它情况使用捕捉的方式

8、异常对象的常用方法

(1)exception.getMessage();获取异常简单的描述信息

                获取异常简单描述信息:这个信息实际上就是构造方法上面String参数

(2)exception.printStackTrace();打印异常追踪的堆栈信息

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

9、异常追踪信息查看技巧

异常信息追踪信息,从上往下一行一行看。

但是需要注意的是:SUN写的代码就不用看了(看包名就知道是自己的还是SUN的。)。主要的问题是出现在自己编写的代码上

10、关于try..catch中的finally子句

(1)在finally子句中的代码是最后执行的,并且是一定会执行的,即使try语句块中的代码出现了异常。没有catch捉住也执行、try里面有return也能执行;(除非System.exit(0);退出jvm不执行)。
        finally子句必须和try一起出现,不能单独编写

退出jvm不在执行

(2)通常在finally语句块中完成资源的释放/关闭,因为finally中的代码比较有保障,即使try语句块中的代码出现异常,finally中代码也会正常执行;不放在finally中当代码出现异常就会无法关闭资源

如:

try里出现异常,导致无法关闭流

放在finally里面关闭

(3)try和finally,没有catch也可以吗

                try不能单独使用。
                try finally可以联合使用

(4)try中由return时执行顺序(return一定是最后执行的)

                先执行try...
                再执行finally...
                最后执行 return (return语句只要执行方法必然结束。)

11、finally执行顺序的一个面试题

按照10.(4)的执行顺序result不是应该时101吗?为什么时100

答:因为

        java语法规则(有一些规则是不能破坏的,一旦这么说了,就必须这么做!):
        java中有一条这样的规则:
            方法体中的代码必须遵循自上而下顺序依次逐行执行(亘古不变的语法!)
        java中还有一条语法规则:
            return语句一旦执行,整个方法必须结束(亘古不变的语法!)

10.(4)规则与上面的规则产生矛盾;sun团队执行了下面的编译方法,解决这些方法的矛盾

找了一个中间变量;使结果达到从代码上看使先return的效果;而又执行了finally里面的代码

        反编译之后的效果

public static int m(){
      int i = 100;
      int j = i;
      i++;
      return j;
}

 12、final finally finalize 的区别

final 关键字:

        final修饰的类无法继承

        final修饰的方法无法覆盖

        final修饰的变量不能重新赋值

finally 关键字:

        和try一起联合使用

        finally语句块中的代码是必须执行的

finalize 标识符:

        是一个Object类中的方法名

        这个方法是由垃圾回收器GC负责调用的

13、自定义异常

13.1 自定义异常方法

SUN提供的JDK内置的异常肯定是不够的用的。在实际的开发中,有很多业务,这些业务出现异常之后,JDK中都是没有的。和业务挂钩的。那么异常类需要我们程序员自己定义

两步:

        第一步:编写一个类继承Exception或者RuntimeException

        第二步:提供两个构造方法,一个无参数的,一个带有String参数的

异常的定义:

异常的使用:

13.2 自定义异常在开发中的应用

作用:

        使用手动抛异常终止程序并让调用者得到异常信息(异常throw后用throws处理)

用法:

        throw new 自定义的异常,throws抛给调用者

常用在登录信息提示那块或者其他需要判断的地方

例1:

自定义异常:

 业务类:

test:

 

例2:

原先的一个代码:数组模拟栈

package com.bjpowernode.javase.array.homework;
/*
	编写程序,使用一维数组,模拟栈数据结构。
	要求:
		1、这个栈可以存储java中的任何引用类型的数据。
		2、在栈中提供push方法模拟压栈。(栈满了,要有提示信息。)
		3、在栈中提供pop方法模拟弹栈。(栈空了,也有有提示信息。)
		4、编写测试程序,new栈对象,调用push pop方法来模拟压栈弹栈的动作。
		5、假设栈的默认初始化容量是10.(请注意无参数构造方法的编写方式。)
 */
public class MyStack {
    // 向栈当中存储元素,我们这里使用一维数组模拟。存到栈中,就表示存储到数组中。
    // 因为数组是我们学习java的第一个容器。
    // 为什么选择Object类型数组?因为这个栈可以存储java中的任何引用类型的数据
    // new Animal()对象可以放进去,new Person()对象也可以放进去。因为Animal和Person的超级父类就是Object。
    // 包括String也可以存储进去。因为String父类也是Object。
    private Object[] elements;

    // 栈帧,永远指向栈顶部元素
    // 那么这个默认初始值应该是多少。注意:最初的栈是空的,一个元素都没有。
    //private int index = 0; // 如果index采用0,表示栈帧指向了顶部元素的上方。
    //private int index = -1; // 如果index采用-1,表示栈帧指向了顶部元素。
    private int index;

    /**
     * 无参数构造方法。默认初始化栈容量10.
     */
    public MyStack() {
        // 一维数组动态初始化
        // 默认初始化容量是10.
        this.elements = new Object[10];
        // 给index初始化
        this.index = -1;
    }

    /**
     * 压栈的方法
     * @param obj 被压入的元素
     */
    public void  push(Object obj){
        if(index >= elements.length - 1){
            System.out.println("压栈失败,栈已满!");
            return;
        }
        // 程序能够走到这里,说明栈没满
        // 向栈中加1个元素,栈帧向上移动一个位置。
        index++;
        elements[index] = obj;
        // 在声明一次:所有的System.out.println()方法执行时,如果输出引用的话,自动调用引用的toString()方法。
        System.out.println("压栈" + obj + "元素成功,栈帧指向" + index);
    }

    /**
     * 弹栈的方法,从数组中往外取元素。每取出一个元素,栈帧向下移动一位。
     * @return
     */
    public void pop(){
        if(index < 0){
            System.out.println("弹栈失败,栈已空!");
            return;
        }
        // 程序能够执行到此处说明栈没有空。
        System.out.print("弹栈" + elements[index] + "元素成功,");
        // 栈帧向下移动一位。
        index--;
        System.out.println("栈帧指向" + index);
    }

    // set和get也许用不上,但是你必须写上,这是规矩。你使用IDEA生成就行了。
    // 封装:第一步:属性私有化,第二步:对外提供set和get方法。
    public Object[] getElements() {
        return elements;
    }

    public void setElements(Object[] elements) {
        this.elements = elements;
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }
}

用手动抛异常代替

System.out.println("压栈失败,栈已满!"); 
 return;

定义异常

改写MyStack

测试(调用)

结果

14、关于方法覆盖中子类不能抛出比父类更多的异常

重写之后的方法不能比重写之前的方法抛出更多(更宽泛)的异常,可以更少

原因:

在使用多态的时候,父类引用指向子类,进而父类引用使用子类的覆盖方法时由于父类没的异常而子类有,就会导致子类方法的异常跟着程跑也没有进行throws或try...catch处理

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值