异常是所有编程语言中都会存在的一个概念。
异常结构
JAVA中关于异常存在三个关键字:try/catch/finally,异常处理格式为:
try {
// statement
} [ catch (异常类型 对象) {
// exception process;
} catch (异常类型 对象) {
// exception process;
} catch (异常类型 对象) {
// exception process;
} ... ][finally {
// statement;
}]
从上面的格式可以看出,异常处理总共存在三种结构:try,try...catch,try...catch...finally。其中try表示异常捕获,catch表示异常处理,finally不管异常是否存在,都会执行。也就是说,finally在这里作为了异常处理的出口,可以用finally来进行log打印。
通常情况下,三种结构都是可以使用的,不过开发中多用finally代码块进行资源释放。但需要注意的是不管是否发生异常,finally代码块都是要执行的。
public class Demo {
public static void main(String args[]) {
System.out.println("Expection start");
try {
System.out.println("try 10/0" + 10/0);
} catch (ArithmeticException e){
System.out.println("Exception");
} finally {
System.out.println("finally");
}
System.out.println("Exception end");
}
}
结果为:
Expection start
Exception
finally
Exception end
从上面的结果可以看出,try代码块出现了异常,该异常被catch之后,打印了catch的相关处理,该异常已经被捕获,但是还是要执行finally代码块的。
另外上边异常出现时候的打印结果是由用户自定义的,除此之外,还可以使用异常类中提供的printStackTrace方法进行异常信息的完整输出。
public class Demo {
public static void main(String args[]) {
System.out.println("Expection start");
try {
System.out.println("try 10/0" + 10/0);
} catch (ArithmeticException e){
e.printStackTrace();
} finally {
System.out.println("finally");
}
System.out.println("Exception end");
}
}
此时的打印结果为:
Expection start
java.lang.ArithmeticException: / by zero
at Demo.main(Demo.java:6)
finally
Exception end
上边的打印说明了是在该文件的第6行出现的,是由于除0导致的。
异常处理流程
之前提到可以用catch来捕获异常,而catch后要跟异常的类型,但是实际使用中,很难预测到出现异常所对应的异常类型。而由于每个异常类都是从最初的父类继承而来的,那么便可以直接使用最初的父类作为接口进行接收。
那么最初的父类是什么,首先看一下两个异常类的继承关系:
//java.lang.ArithmeticException
java.lang.Object
|-java.lang.Throwable
|-java.lang.Exception
|-java.lang.RuntimeException
|-java.lang.ArithmeticException
//java.lang.NumberFormatException
java.lang.Object
|-java.lang.Throwable
|-java.lang.Exception
|-java.lang.RuntimeException
|-java.lang.IllegalArgumentException
|-java.lang.NumberFormatException
从上面的继承关系来看,两者都是来自于Exception这个异常类,那么便可以直接使用该类作为catch的异常类型:
public class Demo {
public static void main(String args[]) {
System.out.println("Expection start");
try {
System.out.println("try 10/0" + 10/0);
} catch (Exception e){
e.printStackTrace();
} finally {
System.out.println("finally");
}
System.out.println("Exception end");
}
}
打印的结果跟之前是一样的。
但其实Exception还有父类Throwable,该类有两个子类:
- Error:指JVM错误,这是的程序并没有执行,无法处理
- Exception:指程序运行过程中出现的异常,用户可以使用异常处理格式处理
而对于JAVA中异常的处理流程可以用下边的图示:
?上边的图示只是将上面的代码进行了可视化,其实原理是一样的。
而既然所有的异常其父类也为Throwable,那么是否可以使用Throwable作为catch代码块的捕获类型。从原则上来讲是可以的,但是因为用户无法处理Error,因此在实际开发中,只需要关注Exception就可以。
而如果开发中对于发生的异常,能够部分猜测到异常的类型,或者想要针对某种特定的异常类型进行特定的处理,不过此时要设置特定的异常处理顺序,一般为小范围在先,大范围在后。
public class Demo {
public static void main(String args[]) {
System.out.println("Expection start");
try {
System.out.println("try 10/0" + 10/0);
} catch (ArithmeticException e){
System.out.println("====================");
e.printStackTrace();
System.out.println("====================");
} catch (Exception e) {
e.printStackTrace();
}
finally {
System.out.println("finally");
}
System.out.println("Exception end");
}
}
此时打印结果为:
Expection start
====================
java.lang.ArithmeticException: / by zero
at Demo.main(Demo.java:6)
====================
finally
Exception end
而如果try代码块中出现的不是ArithmeticException类型的异常,就会被后边的catch代码块捕获。
throws
throws关键字主要在方法定义上使用,表示此方法中不进行异常的处理,而是交给被调用处进行处理。
若没有throws关键字修饰:
public class Demo {
public static void main(String args[]) {
System.out.println("Expection start");
fun();
System.out.println("Exception end");
}
public static void fun(){
System.out.println("fun try 10/0" + 10/0);
}
}
这样的写法是OK的,因为在fun中就会对异常进行处理。此时打印结果为:
Expection start
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Demo.fun(Demo.java:11)
at Demo.main(Demo.java:5)
由于此时出现异常,直接导致程序结束,屏蔽了最后Exception end的打印。
而如果使用throws关键字修饰:
public class Demo {
public static void main(String args[]) {
System.out.println("Expection start");
try {
fun();
} catch (ArithmeticException e){
System.out.println("====================");
e.printStackTrace();
System.out.println("====================");
} catch (Exception e) {
e.printStackTrace();
}
finally {
System.out.println("finally");
}
System.out.println("Exception end");
}
public static void fun() throws Exception{
System.out.println("fun try 10/0" + 10/0);
}
}
上边的代码中必须在该函数调用处进行异常处理,否则编译不过。此时的打印结果为:
Expection start
====================
java.lang.ArithmeticException: / by zero
at Demo.fun(Demo.java:22)
at Demo.main(Demo.java:6)
====================
finally
Exception end
而此时由于异常已经处理过了,最后的Exception end可以正常打印。
throw
throw表示抛出一个异常。之前的异常都是由JVM自动进行实例化操作的,而使用throw可以手动抛出一个实例化对象。
public class Demo {
public static void main(String args[]) {
System.out.println("Expection start");
try {
throw new Exception("My Exception");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("finally");
}
System.out.println("Exception end");
}
}
打印结果为:
Expection start
java.lang.Exception: My Exception
at Demo.main(Demo.java:6)
finally
Exception end
上面代码使用throw手动抛出了一个实例化对象,并进行异常处理,打印了异常的相关信息。
异常处理标准格式
在实际开发中,可能会使用下面的异常处理格式:
public class Demo {
public static void main(String args[]) {
System.out.println("Main start");
try {
fun();
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("Exception process finally");
}
System.out.println("Main end");
}
public static void fun() throws Exception {
System.out.println("Fun start");
try {
System.out.println("Fun 10 / 0" + 10 / 0);
} catch (Exception e) {
System.out.println("Fun exception throw");
throw e;
} finally {
System.out.println("Fun end");
}
}
}
打印结果为:
Main start
Fun start
Fun exception throw
Fun end
java.lang.ArithmeticException: / by zero
at Demo.fun(Demo.java:20)
at Demo.main(Demo.java:6)
Exception process finally
Main end
上边的代码主要有以下几点注意:
- 调用fun时,用户和调用处需要知道fun是否会正常执行,如果Fun start之后打印Fun end表示正常结束,如果存在Fun exception throw,则表明执行异常
- fun内部不需要处理异常,只需要使用throws将可能出现的异常交给调用处处理即可
- fun中的catch中可以捕获可能出现的异常,然后执行某些打印或其它处理,然后将该异常抛出即可
- 上层拿到该异常进行处理
- 有时用户并不关注fun中的执行细节,只需要知道该过程是否正常执行即可
- 由于fun已经使用了throws修饰,表明该方法中的异常是由调用处处理的,因此便可以省略catch代码块,异常也会自动抛出到调用处,不过通常不这么使用,因为取消catch代码块就意味着某些处理一块被取消了,可能会出现误解
上边的格式可以用于数据库操作,将数据库操作封装到fun中,外部不需要知道执行细节,只需要知道是否正常执行即可。
assert
assert是断言的意思,即是对程序执行进行强制判断,若判断失败,则Error。
public class Demo {
public static void main(String args[]) {
System.out.println("Main start");
try {
assert(1 + 1 == 3);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("Exception process finally");
}
System.out.println("Main end");
}
}
使用下面的语句开启断言:
java -ea Demo
打印结果为:
Main start
Exception process finally
Exception in thread "main" java.lang.AssertionError
at Demo.main(Demo.java:6)
上面的代码中,使用assert对语句进行断言,并希望用catch捕获可能出现的异常,但catch语句未执行,说明assert失败后并不抛出异常。而开启断言后,断言失败会出现Error。
RuntimeException
RuntimeException类的性质为:程序在编译时不强制要求用户处理异常,用户可以根据需要选择性地进行处理,但是如果没有处理又发生了异常,就交给JVM默认处理。也就是说该类地子类,可以由用户根据需要选择性地进行处理。
比如之前提到的NumberFormatException异常:
//java.lang.NumberFormatException
java.lang.Object
|-java.lang.Throwable
|-java.lang.Exception
|-java.lang.RuntimeException
|-java.lang.IllegalArgumentException
|-java.lang.NumberFormatException
而int的包装类Integer类的parseInt方法其定义为:
public static int parseInt(String s)
throws NumberFormatException
即该方法可能会抛出NumberFormatException异常,而throws的使用会要求在调用处进行异常处理,而由于RuntimeException类的性质,该方法调用可以写为:
public class Demo {
public static void main(String args[]) {
System.out.println("Main start");
System.out.println(Integer.parseInt("100"));
System.out.println("Main end");
}
}
打印结果为:
Main start
100
Main end
上面的代码中,调用处没有异常处理也会编译通过,这就是由RuntimeException类的性质决定的。而如果一旦出现异常,就交给JVM处理。
自定义异常
既然异常是类,那么便可以进行继承,构建自定义异常:
class MyException extends Exception {
public MyException(String msg) {
super(msg);
}
}
public class Demo {
public static void main(String args[]) {
System.out.println("Main start");
try {
throw new MyException("My Exception");
} catch (Exception e) {
System.out.println("Exception catch");
}
System.out.println("Main end");
}
}
执行结果为:
Main start
Exception catch
Main end
使用自定义异常可以定义用户自己的异常类型,并进行自定义处理。如果要构建自定义工具的话,可以使用自定义异常进行异常处理。
|