Java中发现异常的理想时机是在编译阶段,也就是在运行程序之前。
但是,编译期间并不能找出所有的错误,余下的问题必须在运行期间解决。
异常类型:(继承树)
- java.lang.Object
- java.lang.Throwable(异常类的根类)
- java.lang.Error
- java.lang.Exception
- java.lang.RuntimeException
异常类构造器:
所有标准异常类都有两个构造器:一个是默认构造器;另一个是接收字符串作为参数,以便能把相关信息放入异常对象的构造器
public Throwable() {
...
}
public Throwable(String message) {
...
detailMessage = message;
}
自定义异常:
对异常来说,最重要的是类名,用名称代表发生的问题,并且异常的名称应该可以望文知意。
下面是最简单的自定义异常类:
让编译器自动产生默认构造器,异常类没必要有太复杂的操作,实际中程序员只是查看一下抛出的异常类型。
class MyException extends Exception {
}
异常抛出栈:
记录异常抛出位置
public class Test2 {
static void giveException() {
try {
throw new Exception();
} catch (Exception e) {
// 打印栈中每一个元素
for (StackTraceElement ste : e.getStackTrace()) {
System.out.println(ste.getMethodName());
}
}
}
static void give1() {
giveException();
}
static void give2() {
give1();
}
public static void main(String[] args) {
/*
* giveException
* main
* ---------
* giveException
* give1
* main
* ---------
* giveException
* give1
* give2
* main
* ---------
*/
giveException();
System.out.println("---------");
give1();
System.out.println("---------");
give2();
System.out.println("---------");
}
}
重新抛出异常:
catch(Exception e) {
throw e;
}
重新抛出异常,异常对象的所有信息都得以保持,所以高一级环境中捕获此异常的处理程序可以从这个异常对象中获得所有信息(旧信息),异常栈中依旧是原来异常抛出点的调用栈信息,并非重新抛出异常的信息。
catch(Exception e) {
throw (Exception) e.fillInStackTrace();
}
异常栈中被更新为重新抛出异常位置出的调用栈信息。
public class Test2 {
static void giveException() throws Exception {
throw new Exception();
}
// throw e;
static void give1() throws Exception {
try {
giveException();
} catch (Exception e) {
throw e;
}
}
// throw (Exception) e.fillInStackTrace();
static void give2() throws Exception {
try {
giveException();
} catch (Exception e) {
throw (Exception) e.fillInStackTrace();
}
}
public static void main(String[] args) {
/*
* java.lang.Exception
* at test.Test2.giveException(Test2.java:5)
* at test.Test2.give1(Test2.java:11)
* at test.Test2.main(Test2.java:28)
*/
try {
give1();
} catch (Exception e) {
e.printStackTrace();
}
/*
* java.lang.Exception
* at test.Test2.give2(Test2.java:22)
* at test.Test2.main(Test2.java:34)
*/
try {
give2();
} catch (Exception e) {
e.printStackTrace();
}
}
}
还有一种情况是,捕获异常之后抛出另一种异常,这样和fillInStackTrace()是一样的效果,栈中为新抛出点的信息。
异常链:
捕获一个异常的时候抛出另一个异常,并且希望把原始异常的信息保存下来,这就是异常链。
使用一个cause(因由)对象表示原始异常,通过cause把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最初发生的地方。
在Throwable的子类中,只有三种异常类:Error(用于Java虚拟机报告系统错误),Exception和RuntimeException,提供了带cause参数的构造器。
其他类型异常,则使用initCause()方法来链接异常类。
public class Test2 {
static void giveExcep() throws Exception {
throw new Exception();
}
public static void main(String[] args) {
/*
* Exception in thread "main" java.lang.RuntimeException: java.lang.Exception
* at test.Test2.main(Test2.java:12)
* Caused by: java.lang.Exception
* at test.Test2.giveExcep(Test2.java:5)
* at test.Test2.main(Test2.java:10)
*/
try {
giveExcep();
} catch (Exception e) {
throw new RuntimeException(e);
}
/*
* test.MyException
* at test.Test2.main(Test2.java:19)
* Caused by: java.lang.Exception
* at test.Test2.giveExcep(Test2.java:5)
* at test.Test2.main(Test2.java:17)
*/
try {
giveExcep();
} catch (Exception e) {
MyException my = new MyException();
my.initCause(e);
my.printStackTrace();
}
}
}
class MyException extends Exception {
}
Java标准异常:
Throwable:
- Error:编译时和系统错误(一般不用关心)
- Exception:Java类库,用户方法,运行时故障都可能抛出
特例:RuntimeException
会自动被Java虚拟机抛出,所以不必在异常说明中把他们列出来,他们会被自动捕获。
虽然不用手动捕获RuntimeException,但是可以在代码中抛出RuntimeException类型的异常。
finally子句
try块中的异常是否抛出,finally子句中的代码都会得到执行
用处:
当要把除内存之外的资源恢复到他们的初始状态时:已经打开的文件或网络连接,在屏幕上画的图形,甚至可以是外部世界的某个开关。
当遇到return语句时,也会执行finally子句中的代码,再返回
public class Test2 {
static void f(int i) {
try {
if (i == 0) {
System.out.println("i=0");
return;
}
} finally {
System.out.println("finally...");
}
}
public static void main(String[] args) {
f(0);
}
}
// 输出:
i=0
finally...
异常丢失:
抛出MyException1异常时没有捕获,又抛出了新的异常MyException2,这样,MyException1异常就丢失了。
public class Test2 {
class MyException1 extends Exception {
}
class MyException2 extends Exception {
}
void throwMyExcep1() throws MyException1 {
throw new MyException1();
}
void throwMyExcep2() throws MyException2 {
throw new MyException2();
}
public static void main(String[] args) {
// test.Test2$MyException2
try {
Test2 test = new Test2();
try {
test.throwMyExcep1();
} finally {
test.throwMyExcep2();
}
} catch (Exception e) {
System.out.println(e);
}
}
}
finally中return语句使得程序强制返回,就丢失了异常。
try {
throw new Exception("try");
} finally {
return;
}
异常的限制:
异常中的继承和实现问题:
class MyException1 extends Exception {
}
class MyException2 extends Exception {
}
interface F {
void throwException1() throws MyException1;
}
abstract class Father {
public abstract void throwException0() throws MyException1, MyException2;
public abstract void throwException1() throws MyException2;
}
class Child extends Father implements F {
/*
* 子类继承父类时,抛出的异常可以没有,也可以是父类抛出异常的一部分
*/
// @Override
// public void throwException0() {
//
// }
/*
* 子类继承父类时,抛出的异常可以没有,也可以是父类抛出异常的一部分
*/
@Override
public void throwException0() throws MyException1 {
}
/*
* 要实现的接口和继承的类中有同名方法时,但是抛出的异常不同,
* 子类不可以抛出异常
*/
// @Override
// public void throwException1() throws MyException2 {
// // TODO Auto-generated method stub
//
// }
@Override
public void throwException1() {
}
}
构造器:
构造器中处理异常时,建议将异常重新抛出,因为我们不希望去误导被人对象被正确的成功的创建了。
public Test2() throws Exception {
try {
} catch (Exception e) {
throw e;
}
}
异常处理方式:
- 把异常传递给控制台
由main方法直接抛出
public static void main(String[] args) throws Exception {
throw new Exception();
}
- 把“被检查的异常”转换为“不检查的异常”
try {
} catch (Exception e) {
throw new RuntimeException(e);
}