异常
- 编译时检测:Exception和其子类(除了RuntimeExpection)
- 运行时检测:RuntimeException
编译时异常
继承自Exception的,编译时就检查,一般来自外界因素,或者语法错误,比如有时候读文件,当路径错误或者文件名打错了,就会抛出。这种类型的异常抛出后必须处理,比如在某个方法内可能出现该类型的异常,要么在方法头加上throws继续往上抛,直到某一级catch到为止。要么在方法体里try catch,这里处理过了调用时就不用再处理了。当调用该方法时,也可以用throws或者用try catch。
public class Test {
// 方法体内try-catch
public void run1() {
try {
throw new Exception();
} catch (Exception e) {
e.printStackTrace();
}
}
// 方法头throws给上级调用者
public void run2() throws Exception {
throw new Exception();
}
public static void main(String args[]) /*run2()或者在这里throws*/{
Test t = new Test();
t.run1(); // run1()方法体内已经处理,这里不用catch,也不用在main上throws
// run2()只是throws,仅提示这里可能会抛出,并没有处理,所以这里必须处理,要么继续抛(main函数上throws,要么catch)
try {
t.run2();
} catch (Exception e) {
// 实际开发肯定不能这样,必须进行相应处理
// handle code
e.printStackTrace();
}
}
}
运行时异常
继承自RuntimeException,编译时不会抛出,直到运行到那行代码才抛出,此时问题发生无法让功能正常进行,如数组下边越界,更多是由于调用者的原因(代码写得有问题,比如本应该是arr[i]写成了arr[i+1])或者引发了内部状态改变导致,异常抛出后可处理可不处理,编译让其通过,运行时强制停止,让调用者去处理。即方法头不用加throws,方法体内也不用catch。
public class Test {
// 不用处理
public void run1() {
throws new RuntimeException();
}
public static void main(String args[]) {
Test t = new Test();
t.run1(); // 调用也不处理,程序终止
// 调用时正确处理,程序继续
try {
t.run1();
} catch (Exceptipon e) {
// 实际开发肯定不能这样,必须进行相应处理
// handle code
e.printtStackTrace();
}
}
}
自定义异常类
public class MyException extends RuntimeException {
// 构造函数一般用父类的
MyException(String s) { super(s); }
}
// another file
public class Test {
public void run() {
if (/*expression*/) {
throws new MyException("自定义异常") // 传入实参给了super(s)
}
else {
// code...
}
}
public static void main(String args[]) {
Test t = new Test();
try {
t.run();
} catch (RuntimeException e) {
// handle code
e.printStackTrace();
}
}
}
什么时候用try,什么时候用throws
- 能自己解决的用try,比如数组下标越界,调用者可以事先作判断下标的范围,防止该异常发生。即使造成异常,程序终止,也可以通过修改代码解决。
- 不能自己解决的,比如IO异常,文件没有被关闭,数据库连接异常,访问不存在的路径等,我们没有办法自己搞定,只能上报上级(throws),试想如果路径出错了,根本就没有这个文件。而又try-catch,捕获后程序正常运行,并没有把路径错误给暴露出去。(路径都错了后面的程序能正常运行吗?)这就叫“把异常给吃(隐藏)了”。相反,如果继续抛出,一直到程序终止,用户看到异常信息就知道是路径错了,需要改路径。这样的异常写代码的人根本无法处理,不是改代码就可以的,毕竟程序猿怎么改也可能有人把路径写错:)
异常的其他注意事项
- try -catch还可搭配finally使用,无论捕获异常否,finally里面的内容必然或执行,一般用于关闭文件、关闭连接、释放资源等。
- 也可以不catch,只try-finally。比如某个函数可能抛出异常,我不想处理,但是又必须要把某件事给做了,这是就可以用这种结构。
- throw了多少(n)个异常,就要catch多少(n)个异常。
- 父类方法头若没有throws异常,子类方法覆盖父类时候,子类的方法头不能throw异常。
- throw是确确实实抛出了一个异常,而throws是提醒、标注这儿可能出现异常,并没有实际抛出。
异常的一个形象的例子
上面说的什么时候用try,什么时候用throws还是很抽象的,我也不知带自己在说什么。和实际生活结合起来,看下面这个例子就豁然开朗了,为了直观,部分代码采用中文,注意这很不规范,仅为了便于理解异常。
// 老师用电脑上课
class 电脑蓝屏异常 extends RuntimeException {
电脑蓝屏异常(String s) { super(s); }
}
class 电脑烧坏异常 extends RuntimeException {
电脑烧坏异常(String s) { super(s); }
}
// 电脑烧坏时,午饭自己解决,抛出这个异常个老板,让老板给出解决方案
class 上报老板异常 extends Exception {
上报老板异常(String s) { super(s); }
}
class Computer {
// 电脑运行状态标志位,0位正常运行
private int flag = 1;
public void run() {
if (flag == 1) {
throw new 电脑蓝屏异常("你的电脑蓝屏啦!");
} else if (flag == -1) {
throw new 电脑烧坏异常("你的电脑烧坏啦!");
} else {
System.out.println("老师上课中...");
}
}
public void reset() {
// 重启后正常运行,标志位置0
flag = 0;
// 重启电脑
System.out.println("电脑重启中...");
}
}
public class Teacher {
public static void main(String args[]) throws 上报老板异常 {
Computer pc = new Computer();
try {
pc.run();
} catch(电脑蓝屏异常 e) {
System.out.println("同学们,电脑出了点小毛病,大家等几分钟就好了");
// 蓝屏了可以自己处理,重启一下电脑就行,所以
pc.reset();
pc.run();
} catch(电脑烧坏异常 e) {
System.out.println("同学们,这节课我们不能继续上课了,自习");
e.printStackTrace();
// 烧坏了,没办法自己解决,要么拿去给修电脑的,要么上报给老板买个新的
// 所以继续抛向上级
throw new 上报老板异常("老板..电脑..坏了..需要买新的");
}
}
}
- flag设置为1,说明电脑蓝屏了,运行以上代码,打印如下信息,可以看到,正确处理了异常后,老师又开始上课了。
同学们,电脑出了点小毛病,大家等几分钟就好了
电脑重启中...
老师上课中...
- flag设置了-1,说明电脑烧坏了,会打印以下信息。抛出异常。
同学们,这节课我们不能继续上课了,自习
Test.电脑烧坏异常: 你的电脑烧坏啦!at Test.Computer.run(Teacher.java:21)
at Test.Teacher.main(Teacher.java:39)
Exception in thread "main" Test.上报老板异常: 老板..电脑..坏了..需要买新的
at Test.Teacher.main(Teacher.java:50)
同学们,这节课我们不能继续上课了,自习
注意:以上代码虽然很多中文,但是可以直接编译运行成功的。因为默认编码方式是UTF-8。
by @sunhaiyu
2016.12.12