2. 异常规格(exception specification)
1) 在函数定义时可以声明异常规格。如果一个函数在异常规格中声明了non-RuntimeException异常,那么当调用这个函数时,就一定要捕捉异常规格中的non-RuntimeException异常。
import java.lang.RuntimeException;
import java.lang.NullPointerException;
import java.sql.SQLException;
class TestException{
//(1)异常规格中声明将抛出RuntimeException异常
public void testRuntime() throws RuntimeException {}
//(2)异常规格中声明将抛出NullPointerException异常
public void testNullPointer() throws NullPointerException {}
//(3)异常规格中声明将抛出non-RuntimeException异常
public void testNonRuntime() throws SQLException {}
}
public class Test{
public static void main(String[] args){
TestException te = new TestException();
te.testRuntime(); //(4)
te.testNullPointer(); //(5)
//te.testNonRuntime(); (6)
try{
te.testNonRuntime();
}
catch(SQLException ex){}
}
}
在上述代码中,(1)处在异常规格中声明将抛出RuntimeException;(2)在异常规格中声明将抛出NullPointerException,而NullPointerException是RuntimeException的子类,所以在调用这两个函数时,可不捕捉异常,如(4)(5)处的代码一样直接调用。但(3)处在异常规格中声明将抛出SQLException,而SQLException不是RuntimeException的子类,所以必须捕捉SQLException异常。
2) 如果要在一个函数中抛出non-RuntimeException异常,则必须要在异常规格中声明该异常。
import java.sql.SQLException;
import java.io.IOException;
class Test1{
public void f() throws SQLException{ //(2)
throw new IOException("IOException"); //(1)
}
}
public class ExplicitStatic{
public static void main(String[] args){
Test1 te = new Test1();
try{
te.f();
}
catch(Exception ex){
System.out.println("catch Exception in main");
}
}
}
在(1)处抛出了一个没有在异常规格中被声明的non-RuntimeException异常,在编译时会出错。
2. 取得异常中的信息的几个函数
1) String getMessage()、getLocalizedMessage 、toString
取得异常对象中的信息
import java.lang.RuntimeException;
import java.lang.NullPointerException;
Import java.sql.SQLException;
import java.io.IOException;
class TestException{
public void tSql() throws SQLException {
System.out.println("Originating the exception in tSql()");
throw new SQLException("throw in tSql");
}
}
public class Test{
public static void main(String[] args){
TestException te = new TestException();
try{
te.tSql();
}
catch(SQLException ex){
System.out.println("catch SQLException in main");
System.out.println("ex.getMessage():" + ex.getMessage());
System.out.println("ex.getLocalizedMessage():" +
ex.getLocalizedMessage());
System.out.println("ex.toString():" + ex.toString());
}
catch(Exception ex){
System.out.println("catch Exception in main");
}
}
}
运行结果:
Originating the exception in tSql()
catch SQLException in main
ex.getMessage():throw in tSql
ex.getLocalizedMessage():throw in tSql
ex.toString():java.sql.SQLException: throw in tSql
2) void printStackTrace()、Throwable fillStackTrace()
printStackTrace打印出Throwable和其call stack trace。
FillStackTrace则在调用点设立新的stack trace信息
import java.sql.SQLException;
class TestException{
public static void tSql() throws SQLException {
System.out.println("Originating the exception in tSql()");
throw new SQLException("throw in tSql");
}
public void f() throws SQLException{
try{
tSql();
}
catch(SQLException ex){
System.out.println("In f(), e.printStackTrace()");
ex.printStackTrace();
throw ex; //(1)
//throw (SQLException)ex.fillInStackTrace(); (2)
}
}
}
public class Test{
public static void main(String[] args){
TestException te = new TestException();
try{
te.f();
}
catch(SQLException ex){
System.out.println("catch in main, e.printStackTrace()");
ex.printStackTrace();
}
catch(Exception ex){
System.out.println("catch Exception in main");
}
}
}
结果为:
Originating the exception in tSql()
In f(), e.printStackTrace()
catch in main, e.printStackTrace()
java.sql.SQLException: throw in tSql
void TestException.tSql()
Test.java:5
void TestException.f()
Test.java:9
void Test.main(java.lang.String[])
Test.java:22
java.sql.SQLException: throw in tSql
void TestException.tSql()
Test.java:5
void TestException.f()
Test.java:9
void Test.main(java.lang.String[])
Test.java:22
如果把(1)处代码注释掉,并去年(2)处代码的注释,结果将变成:
Originating the exception in tSql()
In f(), e.printStackTrace()
catch in main, e.printStackTrace()
java.sql.SQLException: throw in tSql
void TestException.tSql() //(3)
Test.java:6
void TestException.f()
Test.java:10
void Test.main(java.lang.String[])
Test.java:24
java.sql.SQLException: throw in tSql
void TestException.f() //(4)
Test.java:16
void Test.main(java.lang.String[])
Test.java:24
由于在代码(2)处设立新的stack trace信息,所以异常会被认为是在f()中发出的,所以在main()中得到的异常原始抛出点为f()(见(3)),而在f()中为tSql()(见(6))。
3) 如果重新抛出一个不同类型的异常,也能产生fillStackTrace()函数的效果。如果把上面代码的f()函数修改成下面的样子:
public void f() throws SQLException,IOException{
try{
tSql();
}
catch(SQLException ex){
System.out.println("In f(), e.printStackTrace()");
ex.printStackTrace();
throw new IOException(); //(1)
}
}
则结果为:
Originating the exception in tSql()
In f(), e.printStackTrace()
catch Exception in main
java.sql.SQLException: throw in tSql
void TestException.tSql()
Test.java:6
void TestException.f()
Test.java:10
void Test.main(java.lang.String[])
Test.java:25
java.io.IOException
void TestException.f()
Test.java:17
void Test.main(java.lang.String[])
Test.java:25
由于在(1)处抛出了一个新的类型的异常,那么在main()中捕捉到的是新的异常,所以在main()中捕捉到的异常的原始抛出点为f()。
3. RuntimeException异常
RuntimeException及其子类所代表的异常我们在程序中不用进行捕捉,如果发生此类异常,Java会自动抛出相应的异常对象,如:
class TestException{
public static void g(int x) {
System.out.println("10/" + x + " = " + 10/x);
}
}
public class Test{
public static void main(String[] args){
TestException.g(2);
TestException.g(0); //(1)
}
}
上面代码在编译时不会发生错误,只有在运行时(1)处会发生错误。虽然除法可能会存在错误,但我们不用进行捕捉,当发生错误时,Java会自动抛出相应异常。
二.以finally进行清理
1. 如果某段代码不管是否发生异常都要执行,那可把它改入finally块中。
import java.sql.SQLException;
class TestException{
public static void tSql() throws SQLException {
System.out.println("Originating the exception in tSql()");
throw new SQLException("throw in tSql");
}
public void f() throws SQLException{
try{
tSql();
}
catch(SQLException ex){
System.out.println("catch SQLException in f()");
throw ex; //(1)
}
finally{
System.out.println("finally in f()");
}
}
}
public class Test{
public static void main(String[] args){
TestException te = new TestException();
try{
te.f();
}
catch(SQLException ex){
System.out.println("catch te.f() SQLException in main");
}
catch(Exception ex){
System.out.println("catch te.f() Exception in main");
}
}
}
运行结果为:
Originating the exception in tSql()
catch SQLException in f()
finally in f()
catch te.f() SQLException in main
虽然在代码(1)处重新抛出异常,但finally块中的代码仍然会被执行。
2. finally造成的异常遗失
如果在finally中执行的代码又产生异常,那么在上一层调用中所捕捉到的异常的起始抛出点会是finally所在的函数。
import java.sql.SQLException;
class TestException{
public static void tSql1() throws SQLException {
System.out.println("Originating the exception in tSql()");
throw new SQLException("throw in tSql1");
}
public static void tSql2() throws SQLException {
System.out.println("Originating the exception in tSql()");
throw new SQLException("throw in tSql2");
}
public void f() throws SQLException{
try{
tSql1();
}
catch(SQLException ex){
System.out.println("catch SQLException in f()");
throw ex; //(2)
}
finally{
System.out.println("finally in f()");
//tSql2(); (1)
}
}
}
public class Test{
public static void main(String[] args){
TestException te = new TestException();
try{
te.f();
}
catch(SQLException ex){
System.out.println("catch te.f() SQLException in main");
System.out.println("getMessage:" + ex.getMessage());
System.out.println("printStackTrace:");
ex.printStackTrace();
}
}
}
运行结果为:
Originating the exception in tSql()
catch SQLException in f()
finally in f()
catch te.f() SQLException in main
getMessage:throw in tSql1
printStackTrace:
java.sql.SQLException: throw in tSql1
void TestException.tSql1()
Test.java:5
void TestException.f()
Test.java:13
void Test.main(java.lang.String[])
Test.java:29
从结果可以看出,在main()中能正确打印出所捕捉到的异常的起始抛出点。但如果去掉代码(1)的注释,结果将变为:
Originating the exception in tSql()
catch SQLException in f()
finally in f()
Originating the exception in tSql()
catch te.f() SQLException in main
getMessage:throw in tSql2
printStackTrace:
java.sql.SQLException: throw in tSql2
void TestException.tSql2()
Test.java:9
void TestException.f()
Test.java:21
void Test.main(java.lang.String[])
Test.java:29
从结果可以看出,在main()中捕捉到的异常是finally中产生的异常,代码(2)中抛出的异常丢失了。