异常的类型
JAVA中的异常主要分为两种,即编译时异常和运行时异常.当然,还有错误,本文中对错误不做过多讨论
编译时异常
编译时异常是指,在编译时由javac编译器(或者其他编译器比如eclipse自带的编译器)报出的异常.这类异常通常是一些不需要运行就可以知道的异常.
下面用一个现实中的比方给出一个编译时异常的例子:
比如,今天我是一个黑社会老大,我找了我的小弟(本文中指编译器和程序),替我办事
例子1
我给了小弟一根牙签(请不要问我怎么定义了这根牙签,这不重要,反正正常人定义的牙签一定没有毒死人的功能)
//Toothpick是牙签的英文,定义了一根新牙签并把它放在牙签盒里(java不会天然有手、桌子这种万能的容器,要有你必须自己造一个容器去装它)
Toothpick y = new Toothpick();
我跟他说"用这根牙签,给我把某个人毒死
//我告诉小弟,使用牙签所具备的poison功能,毒死sb
y.poison(sb);
我们当然知道,牙签,至少是正常的牙签当然不会具备毒死人的功能,所以小弟就会告诉我说
牙签怎么可能毒死人?滚~
The method poison(People) is undefined for the type Toothpick
我们暂时不用着急理解这句话到底是个什么意思,我们只需要知道,他告诉我们,我们没告诉小弟牙签怎么毒死人这一点就可以了.
我们来整理一下,你让你的小弟干了什么
小弟啊小弟,我要造一根牙签
小弟啊小弟,我要请你用牙签把sb给毒死
而小弟因为牙签不可能毒死人,所以至少一般人认为压根没去办这件事,小弟也是这么认为的(事实上发生的事情是,小弟看过了你的功能说明书,你没写它可以毒死人).所以他拒绝去做.
也就是说,因为各种不符合小弟的要求的事情导致你不能编译,那都是编译时异常.另一个例子如下
例子2
我让我的小弟给我建一套房子
//小弟建了一套房子
//然后把这套房子,登记到房管局House,只有这样我们才能在以后找到这套房子
House h = new House();
然后我又跟小弟说,去,把我的房子登记在你们上下班的考勤卡上
//TimeCard是考勤卡的英文
//把之前的房子(其实不是房子,是房子的所在地的指针),登记在考勤卡里.
TimeCard t = h;
然后小弟就又开始抱怨了
考勤卡怎么可能给你登记房地产信息?滚~
Type mismatch: cannot convert from House to TimeCard
当然,其实这句话的信息并不只是不能登记房产信息这么简单,还带有说他没有办法把你的房子变成一个可以登记在考勤表上的东西,但我们其实不着急知道这么多,我们只需要看前半句,他的意思就是说,类型不一样,滚~
我们再来整理一下,你这回让你的小弟干了什么
小弟啊小弟,我要造一套房子
小弟啊小弟,我要请你把这套房子的信息登记到考勤卡上
而小弟因为考勤表不能用来记房地产信息(强记也并非完全不可能,有几种办法,但是这是后话),所以至少一般人认为压根没去办这件事,小弟也是这么认为的(事实上发生的事情是,小弟找了你的产品系列图,发现房子根本不是考勤记录的一种).所以他拒绝去做.
所以,我们的小弟压根不打算去办这件事情,这就是编译时异常.
##运行时异常
那么我们再来看一下运行时异常,什么是运行时异常呢?就是说小弟一开始还没发现有问题,做着做着发现不对了.
下面也给出一个运行时异常的例子,还是那个假设:
今天我是一个黑社会老大,我找了我的小弟,替我办事
例子3
我先让小弟造了三双鞋,s1,s2,s3然后把它们放在各自的鞋盒里
Shoes s1 = new Shoes();
Shoes s2 = new Shoes();
Shoes s3 = new Shoes();
我先让小弟给我造一个能放两双鞋的鞋柜,然后把之前的两双放进去
//这里定义了一个数组.一般情况下,java中的每个容器只能装一样东西,但是数组,List,Map,Set等容器却能装不止一个.
//定义数组的方法是在类的后面加[],具体数组的知识,请专门学习数组了解.
//这里的new Shoes[2]是指这个鞋柜只能放两双鞋
new Shoes[] shoes = new Shoes[2];
//放进两双鞋.java或者说大多数编程语言起始于0,也就是说当计算机数数的时候,0就是1,9就是8
//这一步在鞋柜的第一格和第二个分别放入了两双鞋子
shoes[0] = s1;
shoes[1] = s2;
我让我的小弟把我的第三双鞋也给放进鞋柜
//乍看之下这个代码没啥问题,但是,shoes[2]是指第三双鞋
//所以这里拿的是第三双鞋
shoes[2]=s3;
但我说了,小弟太笨了,他不知道自己刚才才造好的鞋柜只能放两双鞋,所以他会屁颠屁颠跑去看鞋柜,然后他一看,发现:
妈耶,你这个鞋柜根本没有三格好吗?滚~
所以他会抛出一个异常说:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 2
也就是说,在你的main函数的某一个鞋柜(数组)中,出现了一个不该出现的2号元素,而这个鞋柜(数组)根本没这么大.
老规矩,整理一下发生了什么
小弟啊小弟,帮我造三双鞋
小弟啊小弟,帮我造一个两格的鞋柜
小弟啊小弟,帮我把前两双鞋放进前两个格子
小弟啊小弟,帮我把第三双鞋放进第三个格子
然而小弟由于接收到了一个看似完整的信息"把第三双鞋放进第三个格子里",就天真的以为这是OK的,于是跑到了鞋柜前才发现鞋柜没这么大
于是乎,小弟就说"这个老板太坑了我不给他干了,也就是说,你让他在这之后做的事情他就都不会做了,因为程序停止运行了."(程序的运行时异常如果没有处理,程序就会中止运行.)
例子4
我先告诉我的小弟一个数字i,但我想调戏他一下,不给他数字
//Integer是int的包裹类型,int本身并不能被赋值为null,会报编译时异常
Integer i = null;
我让我的小弟把我告诉他的数字加十
//这里不能只写i+10,因为程序会认为这个i+10不是给i用的而是给这一句中可能存在也可能不存在的其他操作用的,所以i不会变化
i=i+10;
小弟很开心的去做了,他知道你给了他一个加数i,并且还给了他一个加数10,最后给了他一个放计算结果的i,他觉得没有任何问题,他也不管你的i具体是什么,只要i经过了初始化就OK(初始化为空也是一种初始化)
结果,他真的算到这一步的时候,发现i是个空.这就把他惹恼了,说
妈耶,你让我加个空是个什么意思呀?滚~
所以他会抛出一个异常说
Exception in thread "main" java.lang.NullPointerException
同样不需要理解,只需要知道他在告诉你,你给了他一个空还叫他做运算.所以老规矩,罢工不干了,这一行后面所有的其他东西都不干了.
异常处理
最简单的异常处理
当然,异常也是可以被处理的.在你不知道异常会不会发生的时候,你可以让小弟去"试一试"(try),下面给出一个例子
例子5
继续延续例子3(放鞋)
现在我让小弟尝试一下有没有第三个格子
//尝试大括号里的事情
try{
shoes[2]=s3;
}
当然光是这样编译器还是会报错的,因为你只叫他尝试一下,这跟啥也没说没啥区别,必须要告诉他失败了怎么办
所以你得告诉他比如:
你去试一下,不管因为什么原因没成功就跳过这个步骤(什么都不做)
try{
shoes[2]=s3;
//捕获一个异常,这里的Exception是指任意的运行时异常,这里的e是指大多数异常会留下的黑匣子,我们暂且不管黑匣子能干嘛
}catch(Exception e){
//大括号里啥也没有,意味着什么都不做,跳到下一条语句
}
这样运行之后,出错时就会跳过了.你也可以在括号里多加一些东西,要注意的是,所有放在try里的语句,不管哪句出错都会跳到后面的catch语句,而try块里后面其他的任何语句即使不出错也都不执行.
捕捉特定的异常
但是,正如我们之前说的,异常不止一种,而每种异常都不一样.我们有时候只能处理其中的一部分异常,而另外的一部分我们没办法处理,因此,我们也可以只抛出一部分异常,剩下一些我们不管,比如我们把前面两个例子合并一下:
例子6
Integer[] in = new Integer[2];
Integer i = null;
in[2] = i+10;
正常来说,这个东西的第三行会告诉我们两个错误,既有数组下标越界(鞋柜没有第三格),也有空指针(数字不存在)
假设我们只想处理数字不存在,至于鞋柜有多大我可不管
那么,我们就可以告诉小弟:
你去试一试,如果没找到数字就用3(但是其他错误我也不知道该干嘛)
//这个柜子不再装鞋子了,他开始装整数了
Integer[] in = new Integer[2];
Integer i = null;
try{
in[2] = i+10;
//只捕获空指针异常,这里的Exception是指任意的运行时异常,这里的e是指大多数异常会留下的黑匣子,我们暂且不管黑匣子能干嘛
}catch(NullPointerException e){
//在整数柜(除了放进去的是一个整数之外基本上跟鞋柜一个道理)里放进一个3
in[2] = 3;
}
当然,这个地方真的要运行的话,小弟给了3还是会报错的,他会说
你只告诉我没找到数字咋办,你可没告诉我没格子咋办,那我不管了,我出错了我就不给你办事了
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 2
但它至少处理掉了我们的空指针.这个步骤的作用我们在下文会继续讨论
定义和抛出一个异常
我们可以定义一个异常类,去声明一个异常,当然它也可以有一些信息,但我们只为了说明白异常是什么做一些最简单的试验,暂时先不管复杂的
例子7
我让我的小弟给我的一个员工(函数)20块钱,让这个员工(函数)去旁边的超市买一个葫芦娃
//buy是一个函数,相当于你的一个员工,你告诉他一些信息或者给他一些东西,他帮你办一件事情然后给你一个(唯一的)回信
buy(20,"葫芦娃");
由于小弟很笨,他就是现实社会中这种只管接受形式条件而不管它是什么的人,他只管他收到了你要他买的东西的名称(“葫芦娃”)就屁颠屁颠去了,这个员工也屁颠屁颠去了.至于有没有你说的葫芦娃他才不帮你判断呢,结果店老板一看,葫芦娃是个什么玩意?不知道,滚~(虽然很多老板会直接抛异常,但我们这里先认为他返回了一个null)
于是小弟就屁颠屁颠带着一个null回来了,说它是六娃(会隐身的那个)(在java中,大多数时候null可以属于任何一个类,这里null也是葫芦娃的一种)
然后呢,你就看着空气哭吧
这时候怎么办呢,你就要告诉员工,如果没有我要买的东西咋办
比如,你告诉员工
你去试着买一下,如果老板说卖完了你就让他进货
try{
buy(20,"葫芦娃");
//Stockout是缺货的英文
}catch(StockoutException e){
//stock是进货
stock("葫芦娃");
}
当然,这样还是不行的,因为相比于其他的运行时异常,JAVA可不知道你说的这个StockoutException是个啥,所以你要定义一个StockoutException,告诉你的员工和老板,什么是一个StockoutException.
//这段写在StockoutException.java中
//extends Exception是指它是异常类的一个子类.只有异常类的子类我们才叫它异常
public class StockoutException extends Exception{
}
并且告诉老板:
如果没货了就抛给顾客一个StockoutException
throw new StockoutException();
当然你会发现这样还是不行,因为老板的思维只有一条线,它才不会管是不是你的员工来买,不管谁来买,只要没货了就会抛出这个异常.
所以,老板得这样告诉所有人:
你来买我就有可能抛给你一个StockoutException
所以,buy函数可能得这样定义
//throws不同于throw,throw是说让它抛出,throws是说我有可能会抛出.类似于英语上的第三人称单数
buy(int money, String toBuy) throws StockoutException
所以,所有买它的人也会了解到并且想办法处理这个异常,要么用try的方法尝试,要么:
跟你的大哥(比如,这个例子中对于员工来说,他的大哥就是你,而对于老板来说,他的大哥就是你的员工)说:啊,我运行到一个StockoutException
即在函数定义中加上
throws StockoutException
需要注意的是,如果main函数接收到了这个异常并且还是没有处理,那程序就会中止.
处理多种异常
当然,我们也可以处理不止一种错误,还是举一个例子
例子8
继续刚才买葫芦娃的例子,有没有可能不是缺货而是没钱呢?当然有,所以我们要想办法让员工更厉害一点,所以我们再定义一个PoorException(由于原理相似,我就不讲怎么让老板和员工知道PoorException和如何让老板抛出这个异常了)并且让员工处理它.
我们告诉员工:
你去拿着这20块钱买个葫芦娃,如果没有葫芦娃,就让老板进货;如果钱不够(PoorException)就去讨饭
即
try{
buy(20,"葫芦娃");
}catch(StockoutException e){
stock("葫芦娃");
//两个名称可以一样,因为不会同时抓到
}catch(PoorException e){
//讨饭
begForFood();
}
所以,如果员工没钱了,就会去讨饭
当然,我们还可以给个兜底的,比如:
你去拿着这20块钱买个葫芦娃,如果没有葫芦娃,就让老板进货;如果钱不够(PoorException)就去讨饭,如果有其它问题,就回来
代码如下:
try{
buy(20,"葫芦娃");
}catch(StockoutException e){
stock("葫芦娃");
//两个名称可以一样,因为不会同时抓到
}catch(PoorException e){
//讨饭
begForFood();
}catch(Exception e){
//回来向我报告,如果我们指定让员工带报告回来,他还必须带来同格式的报告,如return -1;
return;
}
需要注意的是,catch是有顺序的,如果前一条语句catch到了后一条catch就不会执行了,这就好比你告诉员工
给你20块去买个葫芦娃,如果发生问题马上回来,如果老板没货了就让他进货
他不会看最后一部分,他一看到"发生问题马上回来"就已经喜笑颜开了,然后就跑回来了
写累了,留给各位初学java的同学们,希望还能有用,有问题的话可以发邮件到我的qq邮箱584172329@qq.com,我会尽快更正.