本章主要讲解,什么是异常、Java 中的异常体系结构、如何处理异常、如何自定义异常以及异常链的相关知识
1 - 1:Java异常简介
异常简介:异常处理的作用、Java异常体系结构简介
处理异常:try-catch及try-catch-finally、抛出异常、自定义异常、异常链
异常:有异于常态,和正常情况不一样,有错误出现。
阻止当前方法或作用域,称之为异常。
异常好比说一家工厂,本来是正常生产,但突然原料用完了的异常情况,没人处理这个问题,这就出现了工人没法干活,损失客户,老板赔本。如果原料用完时,员工把这样的问题给上级反映,管理人员作出处理,让采购那边订购原料,跟客户说明情况,保证不耽误出货,最后拉闸断电关机器,把所有占用资源释放,老板的损失就会降到最低。
上面这样一说,就是说什么是异常、异常处理的作用和意义。我们来看一下Java中异常体系结构。
在Java中有一个类叫Throwable类,这是异常体系的总类,Java中所有不正常类都继承与它,Throwable主要有两个“儿子”,一个是Error类,一个是Exception类。一般我们很少接触Error这个类,它指的是系统错误,有VirtualMachineError(虚拟机错误)和ThreadDeath(线程死锁),如果出现了Error类,程序完全无法运行了。我们来看Exception类,这个类出现代表着编码、环境、用户操作输入出现问题。Exception也有“儿子”,有RuntimeException(非检查异常)和CheckException(检查异常)。能够引起运行时异常的情况有很多,就像引用了一个空对象的属性或方法,也可能是数组访问越界,又或是类型转换出错,也有可能是算术方面引起的异常:
Runtime(非检查异常):
NullPointerException(空指针异常),如:
String str = null;
System.out.println(str.length());
ArrayIndexOutOfBoundsException(数组下标越界异常),如:
int array = {1, 2, 3};
for(int i = 0; i <= 3; i++){
System.out.println(array[i]);
}
ClassCastException(类型转换异常),如:
class Animal(){
}
class Dog extends Animal(){
}
class Cat extends Animal(){
}
public class Test(){
public static void main(String[] args){
Animal a1 = new Dog();
Animal a2 = new Cat();
Dog d1 = (Dog)a1;
Dog d2 = (Dog)a2;
}
}
ArithmeticException(算术异常),如:
int one = 12;
int two = 0;
System.one.println(one/two);
当然还会有其他异常,要列举会要好多时间。
运行时异常会有Java虚拟机自动抛出,自动捕获,运行时异常的出现大部分情况下说明代码本身有问题,应该从逻辑上改变代码。
再看CheckException(检查异常),比如IOException(文件不存在)、SQLException(链接错误)。
1 - 2:Java中使用try...catch...finally实现异常处理
在Java中,我们使用try...catch或try...catch...finally这样的语句块来捕获并处理异常,我们先来看try...catch语句块。
try{
//一些会抛出异常的方法
}catch(Exception e){
//处理该异常的代码块
}
如果try块中发生异常,就会中止执行程序,然后程序的控制权会交由控制块中的异常处理程序进行处理。catch块中的代码该怎么写,如何处理这个异常则是根据不同业务情景再去不同对待。比如可以发出一些警提示用户或编程人员去检查一些配置、网络连接,也可以在catch中进行错误日志地记录等,就像下面代码:
try{
System.out.print("请输入你的年龄:");
Scanner input = new Scanner(System.in);
int age = input.nextInt();
System.out.println("十年后你" + (age + 10) + 岁);
}catch(InputMismatchException e){
System.out.println("你应该输入整数!");
}
System.out.print("程序结束");
在try块中,首先提示用户输入年龄,通过Scanner对象去尝试捕获从键盘上输入的整数,如果我们输入的不是整数,就会抛出输入不匹配的异常,这个就会被系统捕获进入catch中,输出你应该输入整数。
注意,在try...catch块执行完后在catch块外面的其他语句会顺序的继续执行,最后无论怎样,程序结束都会显示在屏幕上。
如果try抛出很多异常,对于不同异常我们的处理方法不尽相同。如果所有类型异常都用catch块处理,那这个处理就会变得含糊不清,就相当于没有处理了。这时候我们就用到了多重catch块的语法:
try{
//一些会抛出异常的方法
}catch(Exception e){
//处理该异常的代码块
}catch(Exception e){
//处理该异常的代码块
}
来康康这个:
Scanner input = new Scanner(System.in);
try{
System.out.print("请输入第一个数:");
int one = input.nextInt();
System.out.print("请输入第二个数:");
int two = input.nextInt();
System.out.println("两数相除结果为:" + (one/two));
}catch(InputMismatchException e){
System.out.println("你应该输入整数!");
}catch(ArithmeticException e){
System.out.println("除数不能为0");
}catch(Exception e){
System.out.println("我是不知名异常");
}
System.out.println("程序结束");
在try块后面用多个catc块来捕获异常,对其进行相应处理,上面在try块中定义了两个整形变量,one和two,它们都取自键盘上输入的整数值,这时候如果咱们输入第一个“one”的时候,我输入hello,就会抛出输入不匹配异常,程序就会输出你应该输入整数。在输入“two”的时候我们输入了0,程序里就会显示除数不能为0的字样。
写try...catch块注意事项:顺序问题。要按照先小后大,就是先子类后父类的顺序来编写多重catch语句块。因为当程序抛出异常的时候,异常处理系统会就近寻找匹配的处理异常程序,而子类继承于父类,针对于父类的异常处理程序对于子类也是适用的。上面的Exception e是上面异常的父类,所以要把针对于Exception的catc块放到最后。当然如果这个多重catch块顺序写错了,编译器在编译的时候也不会通过,会报错,
我们在写完try...catch语句之后也要做一些善后工作,比如关闭连接、或关闭一些已经打开的文件。而这种善后工作我们就在大括号后面加上finall语句块来进行善后。
try{
//一些会抛出异常的方法
}catch(Exception e){
//处理该异常的代码块
}catch(Exception2 e){
//处理该异常的代码块
}finally{
//最终要执行的代码
}
1 - 3:Java中通过案例学习fry...catch...finally
打开eclipse,建立imooc_exception_demo项目,在src创建个类,放在com.imooc.test包下,叫TryCatchTest类,自动生成main方法:
创建一个test方法,返回int类型值,先暂时让它返回0以免编译器报错。
这个test方法要先给他创建两个变量:divider(除数)、result(结果),try-catch用来捕获while循环,每次循环divider减一,result=result+100/result,如果捕获异常,打印输出抛出异常了,返回-1,否则返回result:
然后我们在main方法里调用一下这个方法了,先建一个try-catch的实例,名字为tct,建立一个int变量叫result接受tct的Task值,然后打印:
package com.imooc.test;
public class TryCatchTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
TryCatchTest tct = new TryCatchTest();
int result = tct.Test();
System.out.println("test()方法执行完毕,返回值是" + result);
}
public int Test() {
int divider = 10;
int result = 100;
try {
while(divider > -1) {
divider--;
result = result + 100/divider;
}
return result;
}catch(Exception e) {
e.printStackTrace();//这个方法显示抛出了什么异常
System.out.println("循环抛出异常了");
return -1;
}
}
}
/*结果:
java.lang.ArithmeticException: / by zero
at com.imooc.test.TryCatchTest.Test(TryCatchTest.java:18)
at com.imooc.test.TryCatchTest.main(TryCatchTest.java:8)
循环抛出异常了
test()方法执行完毕,返回值是-1
*/
确实,Test方法抛出了个异常,这个异常被catch语句块捕获,然后发现并在第18行中显示。
我们再写一个Test2方法,Test2方法和Test方法功能一样,但我们在Test2后面要加一个finally语句块,我们在finally中输出这是finally,哈哈,再输出result值。
package com.imooc.test;
public class TryCatchTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
TryCatchTest tct = new TryCatchTest();
int result = tct.Test2();
System.out.println("test2()方法执行完毕");
}
public int Test() {
int divider = 10;
int result = 100;
try {
while(divider > -1) {
divider--;
result = result + 100/divider;
}
return result;
}catch(Exception e) {
e.printStackTrace();//这个方法显示抛出了什么异常
System.out.println("循环抛出异常了");
return -1;
}
}
public int Test2() {
int divider = 10;
int result = 100;
try {
while(divider > -1) {
divider--;
result = result + 100/divider;
}
return result;
}catch(Exception e) {
e.printStackTrace();//这个方法显示抛出了什么异常
System.out.println("循环抛出异常了");
return result = 999;
}finally {
System.out.println("这是finally,哈哈");
System.out.println("我是result,值是" + result);
}
}
}
/*结果:
java.lang.ArithmeticException: / by zero
at com.imooc.test.TryCatchTest.Test2(TryCatchTest.java:34)
at com.imooc.test.TryCatchTest.main(TryCatchTest.java:8)
循环抛出异常了
这是finally,哈哈
我是result,值是999
test2()方法执行完毕
*/
这里可以看到,在Test2抛出算术异常后,被catch语句块捕获,在方法返回到main方法之前,try调用了finally中的语句,输出了这是finally,哈哈,然后输出我是result,值是999,然后回到main方法。刚才我们讲到了try-catch-finally语句块中的语句和语句块之外的语句的执行顺序的问题,我们再改一下这个方法。我们把result等于999删去,在最后返回1111作为结果。
package com.imooc.test;
public class TryCatchTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
TryCatchTest tct = new TryCatchTest();
int result = tct.Test3();
System.out.println("test3()方法执行完毕,返回值为" + result);
}
public int Test() {
int divider = 10;
int result = 100;
try {
while(divider > -1) {
divider--;
result = result + 100/divider;
}
return result;
}catch(Exception e) {
e.printStackTrace();//这个方法显示抛出了什么异常
System.out.println("循环抛出异常了");
return -1;
}
}
public int Test2() {
int divider = 10;
int result = 100;
try {
while(divider > -1) {
divider--;
result = result + 100/divider;
}
return result;
}catch(Exception e) {
e.printStackTrace();//这个方法显示抛出了什么异常
System.out.println("循环抛出异常了");
return result = 999;
}finally {
System.out.println("这是finally,哈哈");
System.out.println("我是result,值是" + result);
}
}
public int Test3() {
int divider = 10;
int result = 100;
try {
while(divider > -1) {
divider--;
result = result + 100/divider;
}
}catch(Exception e) {
e.printStackTrace();//这个方法显示抛出了什么异常
System.out.println("循环抛出异常了");
}finally {
System.out.println("这是finally,哈哈");
System.out.println("我是result,值是" + result);
}
System.out.println("我是test3,我运行完了!");
return 1111;
}
}
/*结果:
java.lang.ArithmeticException: / by zero
at com.imooc.test.TryCatchTest.Test3(TryCatchTest.java:53)
at com.imooc.test.TryCatchTest.main(TryCatchTest.java:8)
循环抛出异常了
这是finally,哈哈
我是result,值是381
我是test3,我运行完了!
test3()方法执行完毕,返回值为1111
*/
上面那个try块里的while算出来的结果是381,所以值是381。这里可以看出来,如果try-catch里面没有return语句,就会调用这三个语句块之外的语句。
1 - 4:练习题
下列关于 try-catch-finally 语句的描述中,错误的是( )
A、try 语句可以独立存在
B、catch 块跟在 try 语句后面,它可以是一个或多个
C、catch 块有一个参数,该参数是某种异常类的对象
D、多重 catch 语句中,异常类型必须子类在前父类在后
答案:A。解析:try 语句块不可以独立存在,必须与 catch 或者 finally 块同存。