java异常处理机制

java异常处理机制

在编程开发的过程中,程序员可以解决一些程序的bug,但是同样也存在着很多无法避免程序错误,比如客户输入数据的格式不当,读入的文件不在指定路径下等等。我们将程序执行中发生的不正常情况统称为“异常” 。
在我们之前学习的C语言中,我们很少提及异常处理机制,因为机制整体比较复杂,需要程序员定义的部分比较多。而java语言为我们提供了简单易用的异常处理机制,接下来我们从最简单的说起,深入研究一下Java的异常处理机制。

java错误(Error)和异常(Exception)类型

看完上面的描述,你可能会有这样的疑问:异常处理解决的”程序错误“,具体是指哪些错误类型呢?错误和类型有什么区别呢?又该怎么处理呢?接下来我们首先来讨论java的错误类型:

语法错误

语法错误是指由于编程中输入不符合语法规则而产生的,

    int a=0//缺少分号
    String data = (int)a//类型不匹配
    if(data.charAt())//缺少参数
    ...

上面的代码给出了三种常见的语法错误:表达式不完整,缺少必要的标点符号,数据类型不匹配。一般来说,出现这种错误时程序无法正常编译和运行,因此程序员可以很轻易地解决这类问题,不涉及java的异常处理

逻辑错误

逻辑错误指的是,程序逻辑由于程序员的设计不当而出错,没有产生预期的结果,比如:
(1)给数组排序的函数处理过后,数组依然乱序。
(2)二分查找数组中的值,对应的值不在数组中却返回了数组下表。
类似的错误还有很多,这也是我们在学习程序设计和算法设计的时候最容易犯的错误,和之前说的语法错误一样,这类错误也是由程序员通过调试工具来解决,不涉及Java的异常处理。

运行时错误

运行时错误,顾名思义,是程序可以运行,但是在运行过程中产生了一些意想不到的错误导致了程序终止运行,主要类型有:
(1)StackOverflowError:栈溢出
(2)OutOfMemoryError:内存溢出

不同于前两个错误,运行时错误的不同类别似乎有专业的名称去命名,这是因为运行时错误在java的异常处理机制中有专门的类java.lang.Error,定义了常见的运行时错误
现在你可能有一点晕了,我们所说的java异常处理到底处理的是异常还是错误?哪些错误和异常会被处理?别着急,看下面这张图:
在这里插入图片描述

java.lang.Throwable是java定义的程序错误类型(这里的错误是广义的错误,泛指所有的程序运行时不正常的行为,包括了异常和狭义的错误Error),它有java.lang.Error和java.lang.Exception两个子类。
从图中可以看出,我们所说的java异常处理机制,狭义的来说是针对java.lang.Exception这个子类来说的。而java.lang.Error,虽然在java定义的程序错误类型中被定义了,但是由于Error类型是比较严重的错误,因此java程序直接终止,不会做任何的处理。
这下,你应该理解了,上述的三种错误均不是我们讨论的java异常处理机制的核心内容,Exception异常才是java异常处理机制捕获和处理的核心内容

异常(Exception)

我们已经知道,异常就是程序可以处理的"不那么严重"的错误,异常总体上可以分为两大类,检查异常(checked exceptions)和非检查的异常(unchecked exceptions)。

  • 检查异常,就是在编译时就必须处理的异常;如果不进行处理就无法运行。如果这么说还有点抽象,请看下面的例子:
    在这里插入图片描述

这是我刚完成的代码中的一段,可以看到Thread.sleep(100)本身并没有错误,编译器却不允许运行。这是因为这一行代码可能会产生InterruptedException(下面会具体解释),如果不对这个异常进行捕捉,则无法运行。具体的处理方法我们之后再说,在这里我们只要知道常见的检查异常有:
(1)ClassNotFoundException:无法寻找类异常
(2)InterruptException:中断异常
当一个线程处于阻塞状态下的情况下,调用了该线程的interrupt()方法,则会出现InterruptedException。
(3)IOException :输入输出异常

FileWriter file = new FileWriter("abc.txt");//读取文件时,可能因为权限不足,文件名不存在等问题导致输入异常,产生IOException
file.write("aaa");
file.close();

等等等等。
一句话概括,除了之后我们提到的运行时异常(RuntimeException)及其子类,其他所有的Excption都是检查异常,检查异常占了异常中的多数情况。

  • 非检查异常
    和检查异常相对应,非检查异常指的是编译器不要求强制处置的异常,即使可能出现异常也可以不做处理,主要包括了运行时异常(RuntimeException)及其子类,运行时异常(RuntimeException)主要类型如下:
    (1)NullPointerException: 空指针异常
Student lxy = null;//writejava是Student类的一个方法
lxy.writejava();//当使用null.属性或null.方法时会产生空指针异常

(2)IndexOutOfBoundsException :数组越界异常

int []a = new int [6];
int i;
for(i=0;i<=10;i++)
a[i]=1;

(3)ArithmeticException:算数异常
常见的算数异常有除0异常等。

抛出和捕获异常

好了,现在我们已经掌握了异常的基本概念,那么接下来我们的问题就是,java异常处理机制是怎么处理这些异常的呢?

首先,我们要明确什么是抛出异常,什么是捕获异常:
抛出异常,就是java程序的执行过程中如果出现异常,会自动生成一个异常类对象,该异常对象将被提交给Java运行时系统,这个过程就是抛出异常。
这段定义唯一可能难理解的地方,就是"提交给系统",具体提交给系统的那一部分呢?怎么做到提交给系统的呢?

接下来在捕获异常的定义中你可能会找到答案:
如果一个方法内抛出异常,该异常会被抛到调用方法中。如果异常没有在调用方法中处理,它继续被抛给这个调用方法的调用者。这个过程将一直继续下去,直到异常被处理。这一过程称为捕获(catch)异常。如果一个异常回到main()方法,并且main()也不处理,则程序运行终止。
特别的,一个方法不处理它产生的异常,而是沿着调用层次向上传递,由调用它
的方法来处理这些异常,叫声明异常。

从这个定义中我们可以解答之前的问题:
提交异常的位置是不固定的,是层层向外抛出,直到有处理异常的部分或者到main()方法依然未被处理,抛出过程才结束。

我们常见的声明和抛出捕获异常的方式有以下几种:

try-catch方式(抛出捕获异常)

try{
    可能产生异常的代码段...
    ...
}
catch(异常类型 异常名){  
    对异常的处理...
    ...
}

try部分内部的代码段为可能产生异常的代码段,如果这部分产生出了catch段可以捕获的异常(异常类型和catch段定义的异常类型相同),那么执行catch段内的代码,这段代码通常会打印异常信息,常用的函数有(e为异常):

System.out.println(e)
打印异常信息,包括异常名,异常位置等。
e.printStackTrace()
打印异常信息的升级版,不仅能打印异常信息在程序中出错的位置及原因,还能层层向外打印错误位置。

在实际应用中,有一些代码应该无论是否出错都执行(比如程序的退出),我们用finally语句来保证这样的功能实现,那么我们try-cathch语句的实现可以修改为try-catch-finally的实现,最终实现为:

    可能产生异常的代码段...
    ...
}
catch(异常类型 异常名){  
    对异常的处理...
    ...
}
finally{
    一定会执行的语句,通常用来释放资源...
    ...
}

接下来让我们来看一段代码加深理解:

try { 
    x=System.in.read();
}
catch(IOException e){ 
    e.printStackTrace();
}
finally{
    System.out.println("程序结束!")
}

这段代码无论如果产生异常,会打印异常的信息;无论是否产生异常,都会输出"程序结束"。

Throws抛出异常(声明异常)

Throws抛出异常和之前说的try抛出异常最明显的区别在于,try之后会接catch段代码处理异常,但是Throws不会,它会将异常直接返回调用它的方法,无需关心异常被如何处理,因此相对于try-catch来说是一种较为简单的异常声明方法。
我们来看基本格式:

<访问权限修饰符><返回值类型><方法名>(参数列表)throws 异常列表{}

举个例子可能更好理解:

public int compute(int x) throws ArithmeticException

在这个方法中可能会产生ArithmeticException,因此我们提前声明这个异常,并且不用对他进行处理。
那么我们什么时候该选用try-catch,什么时候又该选用throws来声明异常呢?
我们举个例子,如果存在下列的方法调用,那么思考一下这几个方法都该采用什么样的异常处理机制呢?

A调用B,B调用C。

我们从之前的分析可以看出,throws只抛出给调用方法不处理,这意味着我们可以在最开始调用的方法处统一处理(本题中的A),不用在每个方法中都处理一遍,提高了效率。
因此在这个例子中,B和C我们推荐采用Throws抛出异常,最底层的调用A我们推荐采用try-catch捕获处理异常。
或者换句话说,程序中需要处理的异常用try-catch,不需要处理的异常可以用Throws。

Throw抛出异常

在说Throw抛出异常之前,我们必须说明一下自定义异常,因为Throw主要用于抛出自定义的异常。
前面已经说过,java有很多自带的异常类,但是如果可能存在的异常不在这些类中,该怎么处理呢?java允许用户自定义异常,不过不同于java,lang,Exception中的异常可以自动抛出,自定义的异常必须由用户自己想办法抛出,于是我们就有了专门用来抛出自定义异常的方法——Throw。

Throw语句的基本语法如下:

<throw><异常对象>

自定义异常的基本语法如下:

class 自定义异常名 extends Exception//继承于Exception类

我们来看上述三种声明和抛出捕获异常的方式的综合使用:

public class ThrowTest{
    public static void registe(int number)throws MyException{
        if(number<=0)
            throw new MyException(number);
    }
    public static void manage(){
        try{
            registe(-1);
        }
        catch(MyException e){
            System.out.println(e.toString);
        }
        finally{
            System.out.println("manage finish!");
        }
    }
    public static void main(String[] args){
        ThrowTest.manage();
    }
}
class MyException extends Exception{  
    private int x=0;
    MyException(int a){
        x=a;
    }
    public String toString(){
        return "MyException:"+x;
    }
}

我们首先分析自定义异常类MyException,它继承于Exception,构造方法中含有一个int类型参数,并且在重写的toString()方法中返回了这个参数的值.
在main函数中,调用了ThrowTest下的静态方法manage,这个方法中又调用了registe。
我们先看registe。在这个方法中声明了可能出现的异常MyException,只不过这个异常是我们自定义的异常。然后在number<0时,这个自定义的异常被我们的throw抛出(具体实现方式是实例化一个MyException),然后由于throws不作处理,因此返回到了mange函数中。
再看manage函数。由于regist函数可能抛出异常,因此将这段代码放入try段中,在catch中我们打印自定义的报错信息,最后无论是否产生异常我们都执行finally中的语句,表明程序正常结束。

总结

java异常处理的主要知识点如下图所示:
在这里插入图片描述

学习完异常处理的知识,相信聪明的你已经能够提高程序的鲁棒性,写出更加优秀的程序了~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值