程序员人生 网站导航

牛刀小试 - 趣谈Java中的异常处理

栏目:php教程时间:2015-02-02 08:45:46

概述

顾名思义,通俗来说异常就是指,那些产生在我们本来斟酌和设定的计划以外的意外情况。

生活中总是会存在各种突发情况,如果没有做好准备,就让人措手不及。

你和朋友约好了明天1起去登山,半道上忽然乌云蔽日,下起了磅礴大雨。这就是所谓的异常情况。

你1下子傻眼了,然后看见朋友淡定的从背包里取出1件雨衣穿上,淫笑着看着你。这就是对异常的处理。


对1个OO程序猿来说,所做的工作就是:将需要处理的现实生活中的复杂问题,抽象出来编写成为程序。

既然现实生活中总是存在着各种突然的异常情况,那末对应其抽象出的代码,自然也是存在这样的风险的。

所以常常说:要编写1个完善的程序其实不只是简简单单的把功能实现,还要让程序具有处理在运行中可能出现的各种意外情况的能力。

这就是所谓的异常的使用。


体系


这就是Java当中异常体系的结构构成,从图中我们可以提取到的信息就是:

1、Java中定义的所有异常类都是内置类Throwable的子类。


2、Java中的异常通常被分为两大类:Error和Exception:

  • 那末顾名思义,Error代表毛病,Exception代表异常;
  • Error用以指明与运行环境相干的毛病,JVM没法从此类毛病中恢复,此类异常无需我们处理;
  • Exception代表着可以被我们所处理的异常情况,我们需要掌握和使用的,正是该类型。

3、Exception最多见的两种异常类型分别是:

  • IOException:主要是用以处理操作数据流时可能会出现的各种异常情况。
  • RuntimeException:指产生在程序运行时期的异常,如数组越界,入参不满足规范等情况引发的程序异常


工欲善其事,必先利其器

要理解Java中的异常使用,首先要明白几个关于异常处理的工具 - 异常处理关键字的使用。

1、throw:用以在方法内部抛出指定类型的异常。

void test(){ if(产生异常的条件){ thorw new Exception("抛出异常"); } }

2、throws:用以声明 1个方法可能产生的异常(通常都是编译时检测异常)有哪些。

void test() throws 异常1,异常2{ //这个方法可能产生异常1,异常2 throw new 异常1(); throw new 异常2();  }
3、try - catch:另外一种处理异常的方式,与throws不同的是,这类方式是指 "捕获并处理"的方式。

用try语句块包括可能产生异常的代码,catch用于捕获产生的异常,并在catch语句块中定义对捕获到的异常的处理方式。

try { //可能产生异常的代码 } catch (要捕获的异常类型 e) { //对捕获到的异常的处理方式 }
4、finally语句块:通常都是跟在try或try-catch以后进行使用,与其名字代表的1样。也就是定义终究的操作。

特点是被try语句块包括的内容中,是不是真的产生了异常。程序终究都将履行finally语句块当中的内容。

通经常使用于对资源的释放操作,例如:通过JDBC连接数据库等情况。

try { //获得资源 } catch (要捕获的异常类型 e) { //对捕获到的异常的处理方式 }finally{ //释放资源 }

趣解异常的实际使用


了解Java中异常类的实际利用之前,应当先了解两个概念,用以对最经常使用的异常做1个分类:


1、编译时被检测异常:

   只要是Exception和其子类都是,除特殊子类RuntimeException体系。

   所谓的编译时被检测异常也就是指在程序的编译器就会进行检测的异常分类。


   也就是说,如果1个方法抛出了1个编译时检测异常,Java则要求我们必须进行处理。

   既:通过throws对异常进行声明处理 或是 通过try-catch对异常进行捕获处理。

   如果程序编译时检测到该类异常没有被进行任何处理,那末编译器则会报出1个编译毛病。

public class Test{ public static void main(String[] args) { try { Class clazz = Class.forName("Java"); System.out.println(clazz.getName()); } catch (ClassNotFoundException e) { System.out.println("没有找到该类"); } } }

上面代码中的ClassNotFoundException就是1种编译时检测异常,这个异常是由Class类当中的forName方法所抛出并声明的。

如果我们在使用该方法时没有对异常进行处理:声明或捕获,那末该程序就会编译失败。


通过这个例子想要说明的是:编译时被检测异常通常都是指那些“可以被我们预感”的异常情况。

正例如:我们通过Class.forName是想要获得指定类的字节码文件对象,所以我们自然也能够预感可能会存在:

与我们传入的类名参数所对应的类字节码文件对象不存在,查找不到的情况。

既然这类意外情况是可以被预感的,那自然就应当针对其制定1些应对方案。


2、编译时不检测异常(运行时异常):

   就是指Exception下的子类RuntimeException和其子类。

   通常这类问题的产生,会致使程序功能没法继续、运算没法进行等情况产生;

  

   但这类异常更多是由于调用者的缘由或引发了内部状态的改变而致使的。

   所以针对这类异常,编译器不要求我们处理,可以直接编译通过。

   而在运行时,让调用者调用时的程序强迫停止,从而让调用者对本身的代码进行修正。


曾看到过1道面试题:列出你实际开发中最多见的5个运行时异常,就我自己而言,如果硬要说出5个,那多是:

NullPointerException(空指针异常)、IndexOutOfBoundsException(角标越界异常)、ArithmeticException(异常运算条件异常)

ClassCastException(类型转换异常)、IllegalArgumentException(非法参数异常)

public class Test{ public static void main(String[] args) { division(5, 0); } static int division(int a ,int b){ return a/b; } } /* Exception in thread "main" java.lang.ArithmeticException: / by zero at com.tsr.j2seoverstudy.base.Test.division(Test.java:31) at com.tsr.j2seoverstudy.base.Test.main(Test.java:28) */
上面的例子就报出了运行时异常:ArithmeticException。由于我们将非法的被除数0作为参数传递给了除法运算的函数内。

同时也能够看到,虽然“division”方法可能引发异常,但由于是运行时异常,所以即便不做任何异常处理,程序任然能够通过编译。

但当该类型的异常真的产生的时候,调用者运行的程序就会直接停止运行,并输出相干的异常信息。


通过自定义异常理解检测异常和非检测异常

前面我们说到的都是Java本身已封装好提供给我们的1些异常类。由此我们可以看到,秉持于“万物皆对象”的思想,Java中的异常实际上也是1种对象。

所以自然的,除Java本身提供的异常类以外,我们也能够根据自己的需求定义自己的异常类。


这里我想通过比较有趣的简单的自定义异常,结合自己的理解,总结1下Java当中检测异常和非检测异常的使用。

1、编译时检测异常

对编译时异常,我的理解就是:所有你可以预感并且能够做出应对意外状态,都应当通过编译时检测异常的定义的方式进行处理。

举个例子来讲:假定我们开了1家小餐馆,除开正常营业的流程以外。自然可能产生1些意外状态,例如:

菜里不谨慎出现了虫子,出现了头发;或是餐馆突然停电之类的状态。这些状态是每一个经营餐馆的人事前都应当斟酌到的情况。

既然我们已斟酌到了这些意外情况产生的可能性,那末自然就应当针对这些状态做出应对的方案。所以代码多是这样的:

1、首先,定义两个编译时检测异常类,菜品异常和停电异常:

package com.tsr.j2seoverstudy.exception_demo; /* * 菜品异常 */ public class DishesException extends Exception{ public DishesException() { super("菜品有问题.."); } } package com.tsr.j2seoverstudy.exception_demo; /* * 停电异常 */ public class PowerCutException extends Exception{ PowerCutException(){ super("停电异常.."); } }
2、然后在餐厅类当中,对异常作出处理:
package com.tsr.j2seoverstudy.exception_demo; public class MyRestaurant { private static String sicuation; static void doBusiness() throws DishesException, PowerCutException{ if(sicuation.equals("菜里有虫") ||sicuation.equals("菜里有头发")){ throw new DishesException(); } else if(sicuation.equals("停电")){ throw new PowerCutException(); } } public static void main(String[] args) { try { doBusiness(); } catch (DishesException e) { //换1盘菜或退款 } catch (PowerCutException e) { //启动自备发机电 } } }
1、我们已说过了菜品出现问题和停电之类的意外情况都是我们可以预感的,所以我们首先定义了两个编译时检测异常类用以代表这两种意外情况。

2、然后我们在餐厅类当中的营业方法当中做出了声明,如果出现“菜里有虫”或“菜里有头发的问题”,我们就用thorw抛出1个菜品异常;如果“停电”,就抛出停电异常。

3、但是,由于我们抛出这1类异常是由于想告知餐厅的相干人员,在餐厅营业后,可能会出现这些意外情况。所以还应当通过throws告知他们:营业可能会出现这些意外情况。

4、餐厅相干人员接到了声明。因而制定了方案,当餐厅开始营业后。如果出现了菜品异常,请为客人换1盘菜或退款;如果出现停电异常,请启动店里自备的发机电。


2、运行时异常


对运行时异常的使用,我个人觉得最经常使用的情况有两种:

第1、编译时检测异经常使用于定义那些我们可以提供“友好的解决方案”的情况。那末针对另外1些状态,多是我们没法很好的进行解决的。

遇到这类情况,我们可能希望采取1些“强迫手段”,那就是直接让你的程序停止运行。这时候,就能够使用运行时异常。

第2、如果对异常处理后,又引发1连串的毛病的“连锁反应”的时候。


我们先来看1下第1种使用使用情况是怎样样的。例如说:

我们在上面的餐厅的例子中,餐厅即便出现菜品异常或停电异常这1类意外情况。

但针对这1类的意外情况,我们是能够提供较为妥善的解决方案的。

而通过我们提供的针对这些异常情况的解决方案进行处理以后,餐厅照旧营业,顾客接着用餐(程序照旧能够正常运行)。


但还有1种情况,可能不管我们怎样样友好的尝试进行解决,都难以让顾客满意。这类顾客就是传说中被称为“砸场子”的顾客。

针对这类情况,我们可能就要采取更加“强硬的措施”了。例如直接报警把他带走(不让程序继续运行了),这就是所谓的运行时异常:

package com.tsr.j2seoverstudy.exception_demo; //砸场子异常 public class HitException extends RuntimeException { HitException() { super("草,砸场子,把你带走! "); } }
这时候,餐馆类被修改成:
package com.tsr.j2seoverstudy.exception_demo; public class MyRestaurant { private static String sicuation; static void doBusiness() throws DishesException, PowerCutException { if (sicuation.equals("菜里有虫") || sicuation.equals("菜里有头发")) { throw new DishesException(); } else if (sicuation.equals("停电")) { throw new PowerCutException(); } else if (sicuation.equals("砸场子")) { throw new HitException(); } } public static void main(String[] args) { try { sicuation = "砸场子"; doBusiness(); } catch (DishesException e) { // 换1盘菜或退款 } catch (PowerCutException e) { // 启动自备发机电 } } }
因而运行该程序,就会出现:


可以看到出现该运行时异常,程序将直接被终止运行,砸场子的人直接被警察带走了。


那末接下来,我们就能够来看看第2种使用情况了,甚么是所谓的“引发连锁效应的毛病”。

举个例子来讲,以我们上面用到的“被除数为0”的异常情况。你可能会思考:传入的被除数为0,这样的情况我们是可以斟酌到的。

并且我们也能够针对这样的毛病给出对应的措施。那Java为何不将这样的异常定义为编译时检测异常呢?


那末我无妨假定ArithmeticException就是编译时检测异常,所以我们必须对其作出处理,那末可能出现这样的代码:

public class Test {     public static void main(String[] args) {         System.out.println("5除以0的结果为:" + division(5, 0));     }     static int division(int a, int b) {         int num = 0;         try{         num = a/b;         }catch (ArithmeticException e) {             num = ⑴;         }                  return num;     } } }
我们提供了1个进行除法运算的方法,针对传入的被除数为0的异常情况,我们也给出了自己的解决方案:

如果传入的被除数为0,就返回负数“⑴”,“⑴”就代表这个运算出错了。

因而这时候有1个调用者,恰好调用了我们的除法运算方法计算“5除以0的结果”,天经地义的,他得到的结果为:

“5除以0的结果为⑴”。好了,这下叼了,这哥们立马拿着这个运算结果,去向他的朋友夸耀:

你们都是2B吧,算不出5除以0等于多少是吧?告知你们,等于⑴。因而,在朋友的眼中,他成2B了。

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐