异常处理概述

1.1 异常的概念

异常指的是运行期出现的错误,也就是当程序开始执行以后执行期出现的错误。出现错误时观察错误的名字和行号最为重要。
在这个世界不可能存在完美的东西,不管完美的思维有多么缜密,细心,我们都不可能考虑所有的因素,这就是所谓的智者千虑必有一失。同样的道理,计算机的世界也是不完美的,异常情况随时都会发生,我们所需要做的就是避免那些能够避免的异常,处理那些不能避免的异常。这里我将记录如何利用异常还程序一个“完美世界”。

异常情况例如:

  1. 工厂生产,原料用尽
  2. 高速路上车没油
  3. 家里灯泡断电

1.2 异常分类

错误:
错误不是异常,是用户和程序无法控制的问题,这是系统内部的错误。

检查异常:
检查异常通常是用户错误或者不能被程序所预见的问题,例如,打开一个文件,但是文件不存在,这中错误称为检查异常,必须由java语言来处理。

运行时异常:
运行时异常是程序在运行过程中可能发生的,可以被程序员避免的异常,可以被忽略,提示我们开发人员进行处理。

1.3 异常的控制流程

首先来看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Demo01 {
public static void main(String[] args) {
System.out.println("进入main方法");
method1();
System.out.println("退出main方法");
}
public static void method1(){
System.out.println("进入method1方法");
method2();
System.out.println("退出method1方法");
}
public static void method2(){
System.out.println("进入method2方法");
System.out.println("excute method2");
System.out.println("退出method2方法");
}
}
进入main方法
进入method1方法
进入method2方法
excute method2
退出method2方法
退出method1方法
退出main方法

总结:方法调用栈中的顺序为先进后出。

上面的三个方法都被押入到内存的方法栈中,如果说某一段代码出现了异常那代码会如何执行呢?

1
2
3
4
5
6
public static void method2(){
System.out.println("进入method2方法");
int n = 10/0;
System.out.println("退出method2方法");
}

在method2中加入了 int n = 10/0;程序运行到这句话是肯定要抛出异常。那我们会得到什么结果呢?

1
2
3
4
5
6
7
8
9
Exception in thread "main" 进入main方法
进入method1方法
进入method2方法
java.lang.ArithmeticException: / by zero
at com.lovo.test.Demo01.method2(Demo01.java:17)
at com.lovo.test.Demo01.method1(Demo01.java:12)
at com.lovo.test.Demo01.main(Demo01.java:6)
  1. main方法在调用栈的最底部,method2在最顶部,如果method2抛出一个异常,method2就会从栈中被取出,
  2. 同时将异常继续抛给调用他的method1方法,发现method1并没有处理这个异常,那直接抛给main方法,并从栈中退出。
  3. 这时main方法方法也没有处理异常,就有java虚拟机来处理这个异常,虚拟机会创建一个exception对象将信息打印到控制台,然后结束程序。

通过画图来分析异常抛出的流程!

1.4 异常的体系结构

通过api来进行异常类的查看和分析。

1.5 捕获异常

在网上看了这样一个搞笑的话:世界上最真情的相依,是你在try我在catch。无论你发神马脾气,我都默默承受,静静处理。对于初学者来说异常就是try…catch
如果我们发现程序某段代码会抛出异常,那我们要去捕获这个异常。

Java异常处理的五个关键字:try、catch、finally、throw、throws

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 定义一个try块
* 在try块中定义两个变量,int x,int y;
* 每次循环,x-- y+=10/x;
* catch块捕获异常,如果出现异常那就输出 异常
* 返回一个y的值
* @return
*/
public int test(){
try {
int x = 100;
int y = 0;
while(x>-1){
x--;
y +=100/x;
}
return y;
} catch (Exception e) {
e.printStackTrace();
System.out.println("执行try循环抛出异常了");
return -1;
}
}

多个catch块使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 如果有多个catch块,一定要注意异常的类型必须从小到大的定义,从子类父类
*/
public void test(){
try {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个数字");
int num = sc.nextInt();
int res = 100/num;
}catch (ArithmeticException e) {
System.out.println("输入的数字不能为0");
}catch (InputMismatchException e) {
System.out.println("请输入数字");
}catch (Exception e) {
System.out.println("未知的错误");
}
finally{
System.out.println("代码执行完毕,即将保存数据");
}
}

1.6 申明并抛出异常

1.6.1 throw和throws

throw:将异常进行抛出(动作)
throws:声明将要抛出何种类型的异常(声明)

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* throws Exception代表当前方法一旦发生异常将这个异常抛给调用中进行处理
* 如果不知道具体什么异常,可以用exception来代替
* throw 就表示执行抛出异常这个动作
* @throws Exception
*/
public static void method2(int m) throws Exception{
System.out.println("进入method2方法");
if(m==0){
throw new Exception();
}else{
int n = 10/m;
}
System.out.println("退出method2方法");
}

在method方法里面就必须要处理这个异常

1
2
3
4
5
6
7
8
9
10
11
12
public static void method1(){
System.out.println("进入method1方法");
try {
method2(0);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("退出method1方法");
}

1.6.2 重写方法异常

  1. 父类中未抛出异常
    创建一个parent父类
1
2
3
4
5
6
7
8
public class Parent {
//父类中的测试方法,打印相应语句以便测试
public void test() {
System.out.println("parent test");
}
}

子类继承没有抛出异常的父类

1
2
3
4
5
6
7
8
9
10
11
12
public class Child extends Parent {
public void test() throws Exception {
System.out.println("child test");
}
public static void main(String[] args) {
Parent test = new Child();
test.test();
}
}

结果:编译不通过。IDE告诉我们“overridden method does not throw ‘java.lang.Exception’”,好尴尬不是么!不过还好,我们吸取了教训,知道了父类未抛出异常时,子类也不能抛出异常的约定,至少没在项目发布上线后才发现问题,万幸万幸。

  1. 父类中抛出异常
1
2
3
4
5
6
7
8
public class Parent {
//和上面类似,只是抛出了一个异常
public void test() throws Exception {
System.out.println("parent test");
}
}

同样的,再搞一个子类出来,或者也可以将上面的子类稍作修改。如下:

1
2
3
4
5
6
7
8
9
10
11
public class Child extends Parent {
public void test() {
System.out.println("child test");
}
public static void main(String[] args) {
Parent test = new Child();
test.test();
}
}
总结:编译…又报错了。main函数中test.test()这一行有问题,必须要抛出一个异常。try..catch一下,再次编译运行,屏幕打印出child test字样。
1
2
3
4
5
6
7
8
9
10
11
public class Child extends Parent {
public void test() {
System.out.println("child test");
}
public static void main(String[] args) {
Child test = new Child();
test.test();
}
}
总结:编译通过。引用句柄是不是父类类型时,子类可以不用抛出异常。

如果…我子类不是抛出Exception,而是其子类比如IOException呢?

1
2
3
4
5
public void test() throws IOException {
System.out.println("child test");
}
总结:编译通过,main方法里面正常打印出child test

那我把子类和父类的异常掉个包应该也没问题吧:

1
2
3
4
5
6
7
public class Parent {
//父类抛出IOException
public void test() throws IOException {
System.out.println("parent test");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Child extends Parent {
public void test() throws Exception {
System.out.println("child test");
}
public static void main(String[] args) {
Parent test = new Child();
try {
test.test();
} catch (Exception e) {
e.printStackTrace();
}
}
}
总结:竟然又报错了。看来街坊邻居们传的没错,继承体系就是这么“小气”,子类的异常类型必须和父类相同或者是其子类