异常体系:当程序出现错误时能最大化减少损失的一种保护手段。
在Java中一切皆对象,所以异常也是类。
-
先来看一下异常的继承类结构:
只有Throwable
及其子类能够进行异常捕获与处理,所有的异常都是由继承它而来的。 -
Error
:描述JVM运行时的内部错误和资源耗尽错误,如栈溢出,堆溢出。应用程序不抛出此类异常,这种内部错误一旦出现,除了告知用户并使程序安全终止之外,再无能无力,这种情况很少出现。 -
Exception
:程序中普遍存在的由于代码问题产生的错误。在它之下又分为两个分支,RuntimeException
和IOException
。
由于程序错误导致的异常属于RuntimeException;而如果程序本身没有问题,但由于像I/O错误这类问题导致的异常属于IOException。
-
IOException
:由于IO(输入输出)产生的异常,如在程序中打开了一个并不存在的文件。 -
RuntimeException
:发生在运行时异常,如数组越界异常、类型转换异常、空指针(NPE)。
非受查异常:Error、RuntimeException及其子类,不强制用户进行异常处理
当异常产生时,尽量保证异常之后的代码可以正常运行。
受查异常:除了非受查异常的所有异常类都属于受查异常,强制用户进行异常处理。
1. 异常产生
- 观察正确代码:
package yichang;
public class Test {
public static void main(String[] args) {
System.out.println("数学计算开始前");
System.out.println("数学计算进行中"+10/2);
System.out.println("数学计算结束后");
}
}
- 观察有错误的代码:
package yichang;
public class Test {
public static void main(String[] args) {
System.out.println("数学计算开始前");
//零不能做被除数,除0异常
System.out.println("数学计算进行中"+10/0);
System.out.println("数学计算结束后");
}
}
现在程序之中产生了ArithmeticException
异常,但是在异常语句产生之前的语句可以正常执行完毕,而异常产生之后程序直接进行了结束。为了保证程序出现异常后尽量保证异常之后的代码可以继续执行,这时就需要异常处理。
2. 异常处理
异常处理语法格式:
try
[catch...]
[catch...]
...
[finally]
对于以上三个关键字可以出现的组合有:
1. try...[1...N]catch...
2. try...finally
3. try...[1...N]catch...finally
try
:放所有可能出现异常的代码
catch
:当相应异常出现时捕获该异常,然后自定义处理方式
finally
:保证重点代码(如IO流的关闭,JDBC资源的释放以及网络连接的关闭)一定会被执行的机制,无论是否产生异常,finally代码块中的内容一定会被执行。除了系统退出的情况System.exit()
。
- 代码出现异常后使用
try..catch
进行处理:
package yichang;
public class Test {
public static void main(String[] args) {
System.out.println("数学计算开始前");
try {
System.out.println("数学计算进行中"+10/0);
} catch (ArithmeticException e) {
System.out.println("异常已经被处理了!");
}
System.out.println("数学计算结束后");
}
}
出现了异常之后,由于存在异常处理机制,依然可以正常执行完毕。
以上代码虽然进行了异常处理,但是存在一个问题:你现在根本不知道程序产生了什么样的异常。所以为了明确的取得异常信息,可以直接输出异常类对象,或者调用所有异常类中提供的printStackTrace()
方法进行完整异常信息
的输出。
package yichang;
public class Test {
public static void main(String[] args) {
System.out.println("数学计算开始前");
try {
System.out.println("数学计算进行中"+10/0);
} catch (ArithmeticException e) {
e.printStackTrace();
}
System.out.println("数学计算结束后");
}
}
- 使用
try..catch..finally
进行处理:
package yichang;
public class Test {
public static void main(String[] args) {
System.out.println("数学计算开始前");
try {
System.out.println("数学计算进行中"+10/0);
} catch (ArithmeticException e) {
e.printStackTrace();
} finally {
System.out.println("啦啦啦");
}
System.out.println("数学计算结束后");
}
}
不管此时是否产生异常,最终都要执行finally程序代码,所以finally会作为程序统一出口。
以上程序是直接固定好了两个数字进行除法运算,现在通过初始化参数来进行除法运算。
- 初始化参数进行数学运算:
package yichang;
public class Test {
public static void main(String[] args) {
System.out.println("数学计算开始前");
try {
//parseXX:String类转换为基本数据类型
int x = Integer.parseInt(args[0]);
int y = Integer.parseInt(args[1]);
System.out.println("数学计算进行中"+x/y);
} catch (ArithmeticException e) {
e.printStackTrace();
} finally {
System.out.println("啦啦啦");
}
System.out.println("数学计算结束后");
}
}
此时会存在如下问题:
① 用户没有输入初始化参数:ArrayIndexOutOfBoundsException
。
② 用户输入的不是数字: NumberFormatException
。
③ 被除数为0:ArithmeticException
。
以上代码我们发现,通过catch捕获异常的时候如果没有捕获指定异常,程序依然无法进行处理,现在最直白的解决方法就使用多个catch。
- 多个catch块:
package yichang;
public class Test {
public static void main(String[] args) {
System.out.println("数学计算开始前");
try {
int x = Integer.parseInt(args[0]);
int y = Integer.parseInt(args[1]);
System.out.println("数学计算进行中"+x/y);
} catch (ArithmeticException e) {
e.printStackTrace();
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
} catch (NumberFormatException e) {
e.printStackTrace();
} finally {
System.out.println("啦啦啦");
}
System.out.println("数学计算结束后");
}
}
问题又来了,ArrayIndexOutOfBoundsException、NumberFormatException和ArithmeticException都是RuntimeException的子类,那么应该直接用RuntimeException来接收也是没问题的,避免了使用多个catch。
package yichang;
public class Test {
public static void main(String[] args) {
System.out.println("数学计算开始前");
try {
int x = Integer.parseInt(args[0]);
int y = Integer.parseInt(args[1]);
System.out.println("数学计算进行中"+x/y);
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
System.out.println("啦啦啦");
}
System.out.println("数学计算结束后");
}
}
问题是真这么写,真这么测试,不使用异常处理也一样,因为完全可以使用if..else
判断。如果要想更好的处理异常,必须清楚异常的处理流程。
考点:finally
package yichang;
public class Test {
public static void main(String[] args) {
System.out.println(test());
}
public static int test() {
try {
System.out.println(10/2);
return 0;
} catch(ArithmeticException e) {
return 1;
} finally {
return 2;
}
}
}
//结果到底是0还是2??结果是2!!!finally一定会被执行!!!
package yichang;
public class Test {
public static void main(String[] args) {
System.out.println(test());
}
public static int test() {
try {
System.out.println(10/0);
return 0;
} catch(ArithmeticException e) {
return 1;
} finally {
return 2;
}
}
}
//结果是2,finally也会被执行
-
finally
代码块一定会被执行,唯一除了以下情况:系统退出
package yichang;
public class Test {
public static void main(String[] args) {
try {
System.out.println("1");
System.exit(0);
} catch(ArithmeticException e) {
System.out.println("2");
} finally {
System.out.println("3");
}
}
}
//输出1
3. throws关键字
用在方法声明上,明确表示该方法可能会产生异常。当异常产生不希望处理时,使用throws将异常对象抛回给调用者。
package yichang;
public class Test {
public static void main(String[] args) {
try {
System.out.println(caculate(10, 0));
} catch (ArithmeticException e) {
e.printStackTrace();
}
}
public static int caculate(int x, int y) throws ArithmeticException {
return x/y;
}
}
主方法本身也属于一个方法,所以主方法上也可以使用throws进行异常抛出,这个时候如果产生了异常就会交给JVM处理。
package yichang;
public class Test {
public static void main(String[] args) throws Exception {
System.out.println(caculate(10, 0));
}
public static int caculate(int x, int y) {
return x/y;
}
}
4. throw关键字
用在方法中,表示人为进行异常对象的产生。一般与自定义的异常类搭配使用。如果现在异常类对象实例化不希望由JVM产生而由用户产生,就可以使用throw来完成。
package yichang;
public class Test {
public static void main(String[] args){
test();
}
public static void test() {
throw new RuntimeException("抛个异常玩玩");
}
}
面试题:请解释throw和throws的区别 。
① throw用于方法内部,主要表示手工异常抛出。
② throws主要在方法声明上使用,明确告诉用户本方法可能产生的异常,同时该方法可以不处理此异常。