Java基础之异常

一.  异常的概述

        异常简单来说就是我们的程序出现了问题,java中提出了异常处理机制,是为了解决我们程序中出现的问题。Java中通过面向对象的思想把异常封装成了对象,对于不同的异常使用不同的类进行描述。

1.异常的体系:

        Throwable类是java中所有异常的超类,字面意思就是可抛,当我的程序发生了异常,一般我们要把他抛给上一层的调用者,这个类中它有两个子类Error和Exception。

        Error:这种异常非常强大,强大到我们不能够处理,一般就听天由命了,例如说VirtualMachineError这种异常代表java虚拟机崩溃或者用尽了内存,这种异常是由JVM抛出的,我们不可能在程序中给出解决办法,解决这个问题的方式就是修改我们的程序,而且可能是大改。

        Exception:这种异常是表示合理的应用程序想要捕获的条件,就是程序员可处理的异常,而Exception又分为两类,一种是,叫做编译时受检测异常,这种异常我们需要用try…catch…处理,代表着可恢复;另一种是UncheckedException,也叫RuntimeException,代表运行时异常,意为大错已酿成,不可恢复。

        异常体系中子类的后缀名都是父类的名字,阅读性很强。

2.可抛性

        可抛性的体现是通过两个关键字来表示的,throws、throw。

        throw是应用在函数里面代表着我们的代码出现了问题,我们要把他抛出,而抛出的是异常的对象

        Throws是应用在函数的声明上,用来标识我的程序可能会出现的异常,他抛出的是异常类,可以有多个。我的理解是throw是用来往我们的上一级抛异常的,但是首先我们要在我们的方法上声明我这个方法可能会出现的异常,就相当于异常的通行证,只有声明这个异常我们才可以把异常抛出去,当我们抛到上层领导那里后,领导也没有处理,他也抛出了,这时候在领导那里也要用throws声明一下。

        除了声明抛出异常,我们还可以捕捉异常,就是我的程序出现了异常,我不把他往上一层调用者那里抛了,我直接在本地把他处理掉,这就使用到了try…catch…语句,下面会阐述到。

我们通过一个小示例来了解一下抛出的概念:

class ExceptionDemo {
    public static void main(String[] args) {
       int[] arr = new int[]{2,34,23};   
       //传入错误角标位3
      int index = method(arr,3);
    }
    
    //定义一个方法用来返回指定数组指定角标位上的元素
    public static int method(int[] arr,int index)
    {
       //如果传入角标越界,我们就抛出角标越界异常
       if(index>arr.length-1)
           //在异常的构造函数中我们可以指定信息
           throw new ArrayIndexOutOfBoundsException("数组角标越界:"+index);
       
       System.out.println("没有发生异常我就可以出现");
       return arr[index];
    }
}
/*
 * 打印:
 * Exception in thread "main"java.lang.ArrayIndexOutOfBoundsException: 数组角标越界3
   at edu.heima.ExceptionDemo.method(ExceptionDemo.java:13)
   atedu.heima.ExceptionDemo.main(ExceptionDemo.java:7)
 * */

二.自定义异常


        如果我们的程序中出现了一个异常,我觉得java中没有合适的异常类来描述,我可以自定义一个异常类来描述,例如对于角标不存在时java给我们定义了角标越界异常,而对于负数角标的情况java没有定义,这时我们可以自己定义一个类来描述这种异常。

      我们自定义类是用来描述异常的,所以他一定要继承异常体系,只有这样才可以被throw和throws两个关键字所操作。我们以示例来演示说明一下:

/*
 * 自定义异常类用来描述角标为负数的情况
 *这个类必须要继承异常体系,只有这样才能具有可抛性
 * */
 
//我们这里让这个类继承Exception
class NegativeIndexException extends Exception
{
    //定义一个空参的构造函数,我们不用自己定义方法,父类已经给我们定义好了
    NegativeIndexException()
    {
    }
    //定义一个参数为Sting类型的构造函数,我们可以自定义信息以标识异常
    NegativeIndexException(String s)
    {
       //在父类中已经定义好了打印自定义信息的方法,我们利用super关键字调用即可
       super(s);
    }
}
class ExceptionDemo {
 
    /*主函数要调用method方法,而method方法可能会出现负数角标异常,
     * 如果把这个异常抛给我,我也要声明一下这个异常
     */
    public static void main(String[] args) throws NegativeIndexException {
       int[] arr = new int[]{23,22,12};
       //当传入角标位为-1时
       method(arr,-1);
    }
 
    /*我们定义了method方法,这个方法可能会出现负数角标异常,所以我要在定义函数时把
       可能出现的异常问题声明一下*/
    private static int method(int[] arr, int i) throws NegativeIndexException {
       //当角标位为负数时,我们抛出自定义异常类
       if(i<0)
           throw new NegativeIndexException("角标不可以为负数:i="+i);
       System.out.println("异常发生后我就不能出现了");
       return arr[i];
    }
}
/*
 * 打印:
 *Exception in thread "main" edu.heima.NegativeIndexException: 角标不可以为负数:i=-1
    atedu.heima.ExceptionDemo2.method(ExceptionDemo2.java:39)
    atedu.heima.ExceptionDemo2.main(ExceptionDemo2.java:31)
 **/

三.Excepiton异常的分类


        在第一个示例中ArrayIndexOutOfBoundsException数组角标越界我们没有进行throws的声明,发现编译器也没有报错,但是我们自定义异常却要进行throws的声明,原因是因为数组角标越界异常继承自RuntimeException这个父类,而我们自定义的异常直接继承自Exception,我们在开头讲过,RuntimeException是运行时异常,还有一种异常叫做CheckedException编译时受检测异常,那么这两种异常的区别到底在哪?

        CheckedException:在Exception中除了RuntimeException体系中的类都是编译时受检测异常。这种异常是在编译时就要进行处理的,如果你出现了这种异常而你没有进行声明(throw/throws)或者捕获(try…catch),那么编译器就会报错,这是为了让此问题在编译时就有其对应的处理方式,简而言之就是此类问题都可以被处理。例如我们在学习IO时,IOException就是此异常。

        RuntimeException:Excepiton中的RuntimeException和其子类,这种异常是在运行时才会去显现出来的异常,无论你是否进行了处理,编译都不会报错。出现这种异常后,程序便会停止运行,出现的原因大多都是调用者的问题或者内部状态的改变而导致的,这种问题一般都不去处理,等到运行时让程序挂掉,因为一旦出现此异常,你的程序就被判了死刑,就需要修改代码了。所以我们一般是不需要往上层领导那里抛此类异常的,程序都已经挂了还抛出去有什么用,此类异常一般都不用throws声明抛出。


四.异常的捕捉


1.try…catch简析

        其实在以前没有系统的接触异常时,我认为异常就是try…catch,try用于检测,catch用于处理,最后还加上一个finally最终执行。的确,这是异常最为常见的表现形式,我们现在来说一说异常的捕捉,也就是try…catch语句。

      格式:

      try

        {

                需要被检测的代码

        }

        catch(异常类  变量)

        {

                  如果出现了异常,在这里处理

        }

        finally

        {

                  最终执行的代码块

        }

public class classExceptionDemo { 
    public static void main(String[] args) {
       int[] arr = new int[]{23,34,21};      
       System.out.println("异常出现前");
       try {
           //我们打印角标3上的位置,这个角标不存在已经越界
           System.out.println(arr[3]);
           //异常发生后在try代码块中的其他语句不能被执行
           System.out.println("异常在我头上,我还能显示吗");
       }
       //如果发生角标越界异常,就交由catch语句处理
       catch(IndexOutOfBoundsException e) {
           System.out.println("发生了角标越界异常");
       }
       System.out.println("异常发生后经过catch语句处理我还可以显示");
       
    }
}
/*
 * 打印:
 *	异常出现前
   	发生了角标越界异常
   	异常发生后经过catch语句处理我还可以显示
 **/

        通过这个示例我们可以看出try…catch语句是一种预处理体系,即使我的程序发生了异常也能运行完毕,需要说一点的是这个示例中角标越界异常是运行时异常,对于这种异常我们一般是不用try…catch捕获的,因为一旦出现这种异常代表我的程序已经被判了死刑,即使运行下去也没太大意义,所以直接让他停止时最科学的选择,为了演示方便,就偷懒了一下。

2.catch中抛出异常

        我们还可以在捕捉中重新把他抛出去,当我捕捉到这个异常后,我不知道如何处理那么我可以把他抛给上层领导,就是在catch语句中写入throw ,当然必须在函数上使用throws声明。

        对于什么时候使用抛什么时候使用捕获,当我们可以在内部自己处理的时候我们捕捉,当我们无法处理时我们就抛掉他。需要注意的地方是有时候在面试时他们会在try语句中直接抛出异常,注意这个异常是抛给了catch,不是抛给了方法,而且在try中throw语句抛出异常后下面不能再有执行语句了,因为执行不到。

3.多catch的情况

        当我们的代码中可能出现不止一种异常,我们要捕捉到的异常会有多个,这个时候就出现了多catch情况,这种情况有一个需要注意的细节,比如我们定义了多个catch语句来处理try中的代码会发生的异常,但是多个catch语句中有一个父类型catch语句,比如catch(Exception e),这时候父类异常不可以放在子类异常的前面那样编译器会报错,因为按流程语句的特点,当检测到异常后先交给第一个catch语句,如果第一个catch语句参数是父类异常,那么他就直接处理了,而不去访问下面那个具体的子类异常,所以如果多catch中出现了父类异常,一定要放在最下面。

4.finally代码块

        finally代码块里面存放的代码是最终会执行的语句,这个功能一般我们用来做清理工作,因为如果有一些消耗资源的操作,比如IO、JDBC,如果我们使用完没有及时的关闭,那么我的系统内存会一直被占用,当我运行的东西越来越多,而我又不去释放掉这些不用的资源,就造成了内存泄露,异常的出现要求我们无论在什么样的情况下资源都能被及时的清理。

        try…finally结构:这种结构也是保证资源正确关闭的一种手段,我们在try中书写可能出现异常的代码,然后抛给上级,但是资源必须关掉,所以出现了try…finally这种结构。

五.异常的注意事项


1.老虎苍蝇一起打

        用一个Exception捕捉所有的异常,这种使用从代码角度看的确可以捕捉到所有的异常,但是很不幸的是你不知道到底异常出在什么位置上

2.隐藏异常

        当我们的代码出现异常后,我利用try…catch捕捉到并处理了,但是我没有向我的调用者说明该异常发生了,这样就会让应该暴露出来的异常隐藏掉,我们在编写程序时一定要让问题清晰可见,而不是糊弄过去。

3.描述异常

        我们在描述异常时一定要尽量全面的对这个异常进行描述,比如为什么会出现此异常,异常出现的位置,异常有什么解决方式等等。

4.精练try...catch

        将try…catch尽可能的缩短,一定要明确哪些语句是可能产生异常的,不要什么语句都加到try中去。

5.单独处理

        尽量为每一个可捕捉异常写一个try…catch,这样可以避免异常的丢失。

6.子类抛异常注意点

        当子类继承了父类并复写了父类中的方法,如果父类中方法抛出了异常,那么子类就只能抛出父类异常或者此异常的子类,不能抛出比父类更多的异常。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值