异常的定义
在程序的运行中,我们会遇到形形色色的异常:文档读取时出bug;联网播放视频时不停丢包;计算器算数据的时候发现除数是0,等等等等。为了方便统一处理这些问题,大多数面向对象语言都引入了异常的概念:出现问题时抛出一个异常,丢给程序来处理(程序也可以丢给他的上一级,以此类推),这样简化了程序应对突发情况的代码编写难度,提高了代码的容错率(鲁棒性)和可维护性。
Java 的基本异常框架
模板代码及解释
try {
//something
}
catch (Exception e1) {
//solve 2
}
catch (Exception e2) {
//solve 2
}
catch (FileNotFoundException | ParseException e) {
//solve 3 or 4
//紧凑写法,常常用于处理一类处理手法相似的异常
//如果需要分的很清楚,还是分开写吧
//非得这么写,那就用 instanceof 吧
}
//还可以继续接
//
finally {
//最终执行这里的代码
}
我们在 try 代码块中执行可能会抛出异常的语句,一旦检测到异常,立刻和 catch 里面进行比对。
Exception 是一个类,是所有异常的父类(所有的异常都是一个对象,对象里面包含着出错的信息之类的)。我这里图方便只写父类,实际上我们在执行代码的时候是比较清楚他大概会抛出啥异常,所以 catch 还是写仔细点比较好。程序会自动匹配到第一个匹配的 catch 块,随后执行内部语句(执行完之后不会回到 try 块继续运行,而是直接进入 finally)。
不管是顺利 try 完还是碰上了 catch,运行结束后全部进入 finally 环节,执行内部代码。
对于一个异常框架,try 模块必须具备(不然哪来的异常),catch 和 finally 必须有一个(一般都是 catch)。
关于try,catch,finally的执行顺序
1.正常情况
前面我们讲过了大体的运行流程,即try-catch-finally。不过现在我们想要探讨一些更加棘手的问题:有return,抛出异常等扰乱正常程序流程的情况呢?
2. try/catch 语句内部含有 return
public class Main {
public static int test() {
try {
System.out.println(1);
return 1;
}
catch (Exception e) {
System.out.println(1);
return 2;
}
finally {
System.out.println(3);
}
}
public static void main(String []args) {
System.out.printf("result=%d", test());
}
}
执行结果:
1
3
result=1
没错,即使try里面已经返回了,但是 finally 里面还是会运行,执行完了 finally 代码块之后才会return。
3.try/catch 和 finally 内部都含有 return
public class Main {
public static int test() {
try {
System.out.println(1);
return 1;
}
catch (Exception e) {
System.out.println(2);
return 2;
}
finally {
System.out.println(3);
return 3;
}
}
public static void main(String []args) {
System.out.printf("result=%d", test());
}
}
执行结果:
1
3
result=3
与此同时,编译器会警报 finally block does not complete normally ,原因如下:
- 不管 try,catch 里面有没有 return,finally 照常执行
- finally 里面的 return 会覆盖 try 和 catch 里面的 return(如果有的话)
4. finally 内部都含有 return,try/catch 内部会抛出异常
不懂啥叫抛出异常的可以先看下面,待会再来看这个
没错,和 return 类似,即使你抛出异常,但是程序依旧会进入 finally 中,并且正常执行 return,你的上一级程序并不会识别到异常,只会得到 finally 的返回值。5. try/catch 内部含有特殊语句/方法等(例如exit())
finally 再怎么优先,至少是遵循 Java 语法规则的,碰上直接用 exit 这一类系统函数的还是得老老实实退场。finally:try/catch 这两个年轻人不讲武德,来骗,来偷袭,我这个69岁的老同志,这好吗?这不好
总结
- 正常情况下,遵循 try-catch-finally 顺序
- try/catch 中有 return 或者 throw,当执行到这步时,依旧得跳到 finally,先执行完里面的语句,随后再 return 或者 throw(如果finally里面就有 return/throw,那么方法/程序就照着finally 里面这个退回上级了)
- 对于exit,强行删除进程,内存修改这些“超自然”力量而言,以上法则并不适用(逃
异常的分类
1. 可查异常(CheckedException)
这类异常是必须处理的异常,要么 catch 住,要么往外抛,不能不处理。
这种不处理是指代码编写层面的,如果不处理,根本没法编译成功。打个比方,如果你调用一个文件处理的方法,恰好这个文件处理的方法可能会抛出一个可查异常(例如 FileNotFoundException),那么你的代码必须得在运行他的时候准备好处理措施,否则会被拒绝编译。 ```java import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException;
public class TestException {
public static void main(String[] args) {
File f= new File("d:/LOL.exe");
/*
new FileInputStream(f);
没有准备好异常处理就来调用?想得美,直接给你报错怎么说
*/
try{
System.out.println("试图打开 d:/LOL.exe");
new FileInputStream(f); //必须准备好try-catch框架才能执行
System.out.println("成功打开");
}
catch(FileNotFoundException e){
System.out.println("d:/LOL.exe不存在");
e.printStackTrace();
}
}
}
<a name="kbnoF"></a>
## 2. 运行时异常(RuntimeException)
这类异常在程序运行时产生,无需在编码层面进行处理,常见的有除零异常,下标越界异常,空指针异常等等。<br />之所以无需处理,是因为这类异常太常见了,几乎每行代码都有可能出现,如果都要一一处理,那能给程序员写麻了(当然,如果你预见到可能会有异常,还是先准备好处理方法吧)。
```java
public class TestException {
public static void main(String[] args) {
//任何除数不能为0:ArithmeticException
int k = 5/0;
//下标越界异常:ArrayIndexOutOfBoundsException
int j[] = new int[5];
j[10] = 10;
//空指针异常:NullPointerException
String str = null;
str.length();
}
}
3. 错误(Error)
这类异常大多数都是系统级别的,例如内存用光了啥的乱七八糟的。和第二类异常一样,这个也不需要强制处理
附:Error 和 Expection 是同一级的(所以严格讲,错误并不是异常的一种),他们有一个共同的父类 Throwable。
抛出异常
有时候我们并不想自己在本方法内部处理异常,而是将其抛给上一级,那么我们可以使用 throws 来解决。
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class TestException {
public static void main(String[] args) {
method1();
}
private static void method1() {
try {
method2();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
private static void method2() throws FileNotFoundException {
File f = new File("d:/LOL.exe");
System.out.println("试图打开 d:/LOL.exe");
new FileInputStream(f);
System.out.println("成功打开");
}
}
在函数后面加上 throws,表明这个函数可能会抛出一个异常。
不难注意到,method2 方法有可能产生一个异常,那干脆都不用自己 throw 了,检测到之后发现没准备 try-catch框架,直接自动给扔出去了(逃
throw 具体咋用,等到自定义异常那边再细讲吧
注:如果框架只有 try 和 finally,那你必须得在函数上加上 throws。
自定义异常
很多时候,系统自带的异常并不能够满足我们的需要,所以我们常常需要自己编写异常。
class GcdException extends Exception {
String errInf = "gcd的两个数并非都是正整数";
public void printErr() {
System.out.print(errInf);
}
}
class MyMath {
public static int gcd(int a, int b) throws GcdException{
if (a < 0 || b < 0)
throw new GcdException();
if (a == 0) return b;
if (b == 0) return a;
return gcd(b, a % b);
}
}
public class Main {
public static void main(String []args) {
int a = 36, b = -24;
try {
System.out.printf("gcd is %d", MyMath.gcd(a, b));
}
catch (GcdException e) {
e.printErr();
}
}
}
所有的自定义异常都是可查异常,所以在使用的时候必须搭好 try-catch 框架。