1 - 5:Java中的异常抛出及自定义异常
Java中异常的抛出有两个关键字:throw和throws。下面是throws的用法,使用在方法里的,用来声明将要抛出何种类型的异常(声明)。throws是写在方法名和方法体的参数表之后,在方法体之前。用它修饰的方法向调用者表明该方法可能会抛出某种类型的异常。在这里可以之抛出一种类型异常也可以抛出多种类型。每个类型异常中间用逗号隔开。具体的方法体里可以调用一些会抛出异常的方法,或者可以先抛出一个异常,着里就用了一个throw关键字。throw是个动词,他写在方法体里面。它表明的就是具体的抛出异常这个动作。如果某个方法调用到了会抛出异常的方法,那必须添加try-catch语句去尝试捕获这种异常,或者添加throws声明将异常抛出给更上面一层的调用者处理。
这是throws用法:
public void 方法名(参数列表){
throws异常列表{
//调用会抛出异常的方法或者:
throw new Exception();
}
}
我们看一下这个代码:
public void divide(int one, int two) throws Exception{
if(two == 0){
throw new Exception("两数相除,除数不能为0!");
}else{
System.out,println("两数相除,结果为" + one/two);
}
}
在方法divide中,声明抛出Exception的异常,当变量two为0,而会抛出一个新的Exception异常,信息是两数相除,除数不能为0。
再看下面:
public void compute(){
/*
*
*此处省略计算代码
*
*/
try{
divide(5, 0);
}catch(Exception e){
System.out.println(e.getMessage());
}
}
就像咱们列举了可以处理的情况,通过try-catch语句块尝试捕获处理divide方法中抛出的异常。
还有一个方法:
public void compute() throws Exception{
/*
*
*此处省略计算代码
*
*/
divide(5, 0);
}
是向咱们演示了不能处理的情况。当调用者不能处理异常的时候,则将该异常继续声明抛出给更上一层的调用者处理。
这就像工厂生产过程中的出现原料用完了,员工对于这种异常无能为力,要向上级反映处理。虽然Java标准类库中提供了很丰富的异常种类,但可能也会遇到我们实际应用情景用到了Java类库中没有的异常,比如我想要一个喝大了异常,类库很定没有。这时候我们就出现了有“自定义异常”的概念。自定义异常就是咱们自己定义的异常类型,这个自定义异常必须继承Java标准类库中意思相近的异常类型,或者直接继承所以异常类型的积累,就是Exception类型。
现在就写一个自定义类型吧,刚才我们说喝大了这个类型,在Eclipse里新建一个类,叫DrunkException,后面要继承异常的类型,这边继承Exception类,咱们也可以想它的父类Exception那样,为自定义异常添加一个带有一个字符串类型参数的构造器,而咱们只需要在含参的构造器中调用它的父类Exception的构造方法就可以,然后再把参数传进去,因为有的因为有了一个含参构造器,编译器就不会为咱们的自定义异常类补充一个无参的构造器。而有时候我们又需要用到,那我们再手动添加一个无参构造器。
public class DrunkException extends Exception {
public DrunkException() {
}
public DrunkExceptipn(String message) {
super(message);
}
}
//第3-8行都是构造器
多数情况下咱们写到这自定义异常就写完了,我们也可以给自定义异常添加更多相对复杂的功能。
1 - 6:练习题
自定义异常类的父类可以是( )
A、Error
B、VirtuaMachineError
C、Exception
D、Thread
答案:C。解析:Exception 是异常类,自定义异常要继承于 Exception 类或者其子类
1 - 7:Java中的异常链
有时候我们能把捕获的异常包装成一个新的异常,然后在新的异常里添加对原始异常的引用,再把这个新异常抛出。他们就像是链式反应一样,一个导致另一个。在Java中,这种情况叫做异常链。我们看看代码吧。
我们还在test包下新建一个类,类名叫ChainTest,生成main方法,这个类完成的工作:第一个方法(Test1):抛出“喝大了”异常;第二个方法(Test2):调用Test1,捕获“喝大了”异常,并包装成运行时异常,继续抛出;main方法中调用test2,尝试捕获test2方法抛出的异常。
先写第一个方法test1:
看到这里有个错误,点一下这个错误再点一下第15行这句话,会发现这样的情况:
实际上就是没有添加抛出声明,双击抛出声明,变成这样就没问题了:
然后看test2,这样写:
发现还有错,test1里面其实是抛出了一个DrunkException,但在test2没对它进行处理,所以我们要在这里加一个try-catch包围,然后再catch块中咱们会把DrunkException包装成一个新的运行时异常然后抛出。
咱们先新建一个RuntimeException,起名叫newExc,RenException newExc = new RuntimeException("司机一滴酒,亲人两行泪");,然后调用newExc的initCause方法并把捕获的DrunkException放进去,然后抛出新异常:
然后我们要在main方法中调用test2,在main中先创一个ChainTest实例,然后用try-catch去包围ChainTest实例的test2方法,catch后面写Exception e,然后调用一下Exception的printStackTrace方法,打印一下就可以了。
全部代码:
package imooc.exception.test;
public class ChainTest {
/*
* 这个类完成的工作:第一个方法(Test1):抛出“喝大了”异常;
* 第二个方法(Test2):调用Test1,捕获“喝大了”异常,并包装成运行时异常,继续抛出;
* main方法中调用test2,尝试捕获test2方法抛出的异常
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ChainTest ct = new ChainTest();
try {
ct.test2();
}catch(Exception e) {
e.printStackTrace();
}
}
public void test1() throws DrunkException {
throw new DrunkException("喝车别开酒!");
}
public void test2() {
try {
test1();
} catch (DrunkException e) {
// TODO 自动生成的 catch 块
//e.printStackTrace();这个就不需要了
RuntimeException newExc = new RuntimeException("司机一滴酒,亲人两行泪"/*e*/);//新建一个RuntimeException,起名叫newExc
newExc.initCause(e);//调用newExc的initCause方法并把捕获的DrunkException放进去
throw newExc;//抛出新异常
}
}
}
/*
控制台结果:
java.lang.RuntimeException: 司机一滴酒,亲人两行泪
at imooc.exception.test.ChainTest.test2(ChainTest.java:30)
at imooc.exception.test.ChainTest.main(ChainTest.java:14)
Caused by: imooc.exception.test.DrunkException: 喝车别开酒!
at imooc.exception.test.ChainTest.test1(ChainTest.java:21)
at imooc.exception.test.ChainTest.test2(ChainTest.java:26)
... 1 more
*/
这里可以看到控制台输出,第一行java.Lang.RuntimeException就是说咱们在main方法中捕获了一个运行时异常,然后提示信息是司机一滴酒,亲人两行泪,下面看caused by,由此句可以看出该运行时异常是由喝大了的异常引起的,而喝大了的异常是在21、26行抛出的。这就像刚才说的,新的异常中包含原始异常的所有信息,根据这点,咱们可以一行一行的追溯最初异常的位置。这个例子中咱们通过调用新异常的(这里有个方法但我没听清),去引用了原始异常,从而实现了异常链的功能。
其实我们还有一种比较简便的方法:
首先定义到test2,把RuntimeException newExc = new RuntimeException("司机一滴酒,亲人两行泪"/*e*/);中的司机一滴酒,亲人两行泪删掉,在里面直接输入e,然后把newExc.initCause(e);注释,在运行,结果为:
/*
java.lang.RuntimeException: imooc.exception.test.DrunkException: 喝车别开酒!
at imooc.exception.test.ChainTest.test2(ChainTest.java:30)
at imooc.exception.test.ChainTest.main(ChainTest.java:14)
Caused by: imooc.exception.test.DrunkException: 喝车别开酒!
at imooc.exception.test.ChainTest.test1(ChainTest.java:21)
at imooc.exception.test.ChainTest.test2(ChainTest.java:26)
... 1 more
*/
我们首先看到捕获的是运行时异常,该运行时异常是由一个喝大了的异常引起的。下面Caused By的喝大了显示出喝车别开酒。
上面这两种写法就是咱们实现链功能的两种基本写法。
1 - 8:练习题
下列关于异常的描述中,错误的是( )
A、Exception 的父类是 Throwable
B、使用 try-catch-finally 语句捕获并处理异常
C、可以使用 throw 语句抛出异常
D、捕获到的异常只能用当前方法中处理,不能用其他方法处理
答案:D。解释:捕获到的异常,可以在当前方法的 catch 块中处理,也可抛出给调用者去处理
1 - 9:经验总结
1、处理运行时异常时,采用逻辑去合理规避的同时辅助try-catch处理。这样基本就能做到没有漏网之鱼。
2、在写完多重catch块之后咱们还可以加一个catch(Exception)来处理可能会被遗漏的异常。
3、对于不确定的代码,也可以加上try-catch,处理潜在异常。
4、对于异常我们要尽量处理,因为异常说明很多问题,比如程序问题或环境问题等,如果不去处理程序就会在健壮性上就会大打折扣,而在处理过程中,最忌讳的就是仅仅调用printStackTrace()去打印输出或用系统输出方法打印输出,最好是打印输出异常原因同时加以其他操作,比如业务回滚。
5、具体如何处理方法,还要根据不同业务需求应用场景及不同异常类型去灵活应变。
6、尽量添加final语句块去释放占用资源,尤其是有网络连接和连接数据库的情况下。
模拟借书系统
要求:
1、定义字符串数组保存图书信息。2、提示用户输入,分别按“书名”、“图书序号”查找图书。3、根据输入信息进行适当异常处理:ⅰ如果输入类型错误,抛出“错误命令异常”并提示重新输入,ⅱ如果书名不存在,抛出“图书不存在异常”并提示重新输入,ⅲ如果图书序号超过字符串数组范围,抛出“图书不存在异常”提示重新输入。