Exception in Java
I. 异常的由来
异常, Exception, 就是程序出现了不正常的情况。程序的异常,有下面几种
- 程序的异常: java.lang.Throwable: 是所有异常类的父类
- 错误:严重问题,Error
- 异常:问题, Exception
- 编译期的问题: 非 RuntimeException 的异常
- 运行期的问题: RuntimeException 异常
图示如下:
II. 错误,Error
java.lang.Error:
- Error 是 Throwable 的子类,用于指示合理的应用程序不应该试图捕获的严重问题
- Error异常我们一般不处理,这种问题一般都是很严重的,比如说 内存溢出
III. 运行期异常,RuntimeException
java.lang.RuntimeException:
- 所有的 RuntimeException 类及其子类的实例被称为运行时的异常,其它异常就是编译时异常
- RuntimeException 是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类
- 可能在执行方法期间抛出但未被捕获的 RuntimeException 的任何子类都无需在 throws 子句中进行声明
- RuntimeException 这种问题我们可以不处理,因为这个异常的出现是因为代码不严谨,需要进行修正代码
- 当然也可以显式的处理 RuntimeException
IV. 编译期异常
不是 RuntimeException 的异常,必须要显式的进行处理,否则,编译不通过
V. 异常的处理
1. 默认处理
如果程序出现了问题,我们没有做任何处理,那么 JVM 会作出默认处理:
- 默认处理: 把异常的名称,原因以及出现的问题等信息输出在控制台,同时会结束程序的执行
package org.lovian.exception;
public class ExceptionDemo {
public static void main(String[] args) {
int a = 10;
int b = 0;
System.out.println("a/b = " + a/b);
}
}
result:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at org.lovian.exception.ExceptionDemo.main(ExceptionDemo.java:8)
在这个例子中,我们没有对异常没有做任何的处理,从而 JVM 会给出异常的信息,同时结束程序
2. 异常的处理方案
A. try…catch…finally
我们可以用 try/catch 去处理异常,当异常发生的时候,我们可以通过代码来处理这个异常
处理一个异常
- 格式
try {
可能出现问题的代码;
}catch(异常名 异常变量){
针对问题的处理;
}finally {
释放资源;
}
- 变形格式
try {
可能出现问题的代码;
}catch(异常名 异常变量){
针对问题的处理;
}
- 注意:
- try 中的代码越少越好
- catch 中必须有内容,哪怕是给处一个异常提示,否则异常就会被隐藏
我们用 try catch 来处理上面那个异常
package org.lovian.exception;
public class ExceptionDemo {
public static void main(String[] args) {
int a = 10;
int b = 0;
try{
System.out.println("a/b = " + a/b);
}catch (ArithmeticException e) {
// TODO: handle exception
System.out.println("b can't be 0");
e.printStackTrace();
}
}
}
result:
b can't be 0
java.lang.ArithmeticException: / by zero
at org.lovian.exception.ExceptionDemo.main(ExceptionDemo.java:9)
可以看见异常被处理了,打印了不能为0的信息
处理多个异常
我们可以用 try/catch 来一个一个处理异常,比如下面的代码,就处理了运算异常和数组越界异常:
package org.lovian.exception;
public class ExceptionDemo2 {
public static void main(String[] args) {
multiException();
}
public static void multiException(){
int a = 10;
int b = 0;
try{
System.out.println(a/b);
}catch (ArithmeticException e) {
System.out.println("b can not be 0"); // 这是一个运行期异常
}
int[] arr = {1, 2, 3};
try{
System.out.println(arr[3]); // 这是一个运行期异常
}catch (ArrayIndexOutOfBoundsException e) {
System.out.println("access index out of array bounds");
}
}
}
result:
b can not be 0
access index out of array bounds
但是,每一次都要去一个一个捕获每一个异常,是非常麻烦的,所以 java 允许用 try/catch 去一次性捕获多个异常,格式如下:
- try/catch 处理多个异常格式
try {
可能出现问题的代码;
}catch(异常名 异常变量){
针对问题的处理;
}catch(异常名 异常变量){
针对问题的处理;
}
...
finally {
释放资源;
}
代码如下:
package org.lovian.exception;
public class ExceptionDemo2 {
public static void main(String[] args) {
multiException();
}
public static void multiException(){
int a = 10;
int b = 0;
int[] arr = {1, 2, 3};
try{
System.out.println(a/b);
System.out.println(arr[3]);
}catch (ArithmeticException e) {
System.out.println("b can not be 0");
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("access index out of array bounds");
}catch(Exception e){
System.out.println("error");
}
System.out.println("over");
}
}
result:
b can not be 0
over
- 注意:
- 通过代码我们可以发现,一旦 try 语句块中的代码被捕获异常,那么程序就会转向执行相应的 catch 语句块, 原先 try 中的代码不会被继续执行,转而执行 try/catch 语句块之外的代码
- 有时候我们可能不知道什么地方会出什么样的异常,所以在代码最后,可以用 catch 来捕获 Exception。 当 try 语句块中发生异常之后,如果 jvm 没有在 catch 语句块中找到对应的异常,那么它就会匹配 Exception 来处理
- 能够明确的异常就尽量明确
- 平级的异常谁先谁后无所谓,但如果出现了子父关系,那么,子异常必须在父异常的前面
- 另一种格式(JDK7 新特性)
JDK7 之后提出一个新特性可以在一个 catch 块中捕获多个异常。但是这个方式虽然简洁,但是也有缺陷,多个异常使用一个方式来处理,所以这些异常类型应该是统一类型的问题; 另外在一个 catch 中只能捕获平级的异常,不能出现子父类异常同时出现在一个 catch 中
public static void multiException(){
int a = 10;
int b = 0;
int[] arr = {1, 2, 3};
try{
System.out.println(a/b);
System.out.println(arr[3]);
}catch (ArithmeticException | ArrayIndexOutOfBoundsException e) {
System.out.println("error");
}catch(Exception e){
System.out.println("error");
}
}
try/catch 匹配异常
通过上面的例子,我们发现 JVM 捕获异常,然后处理异常。其实是在 try 语句块中发现问题后, JVM 会生成一个异常对象,然后把这个对象抛出,和 catch 中的类进行匹配。 如果该对象是某个类型的,就会执行该 catch 里面的处理信息。
异常中要了解的几个方法,方法定义在所有异常的父类 Trowable 中:
public String getMessage()
: 异常的消息字符串public String toString()
: 返回异常的简单信息描述- 此对象的类的 name (全路径名)
- ”: “ (冒号和一个空格)
- 调用此对象 getLocalizedMessage() 方法的结果
public String getLocalizedMessage()
: 默认返回与 getMessage() 相同的结果, 子类可以重写此方法用于生成特定的信息public void printStackTrace()
: 获取异常类名和异常信息,以及异常出现在程序中的位置,返回值 void,把信息输出在控制台- 注意,使用这个方法不会终止程序,还会继续执行 try/catch 语句块之外的代码
public void printStackTrace(PrintStream s)
: 通常使用该方法将异常内容保存在日志文件中
B. throws
定义功能方法时,需要把出现的问题暴露出来让调用者去处理,那么就通过 throws 在方法上进行标识, 示例代码如下:
package org.lovian.exception;
import java.text.ParseException;
import java.text.SimpleDateFormat;
public class ExceptionDemo3 {
public static void main(String[] args) {
System.out.println("begin");
try {
method();
} catch (ParseException e) {
// handle exception
e.printStackTrace();
}
System.out.println("end");
}
private static void method() throws ParseException{
String s = "2016-07-25";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 这是一个编译期异常, 必须要处理,要么 throws 要么 try/catch
Date d = sdf.parse(s); // throw exception
System.out.println(d);
}
}
为什么要用这种方式呢? 原因是因为,有些时候,我们可以直接处理异常,但是有些时候,我们根本没有权限去处理某个异常,通俗点说,我处理不了,就不处理了,把这个异常交给调用该方法的人去处理。 所以 Java 针对这种情况,提供了 throws 处理方法。
throws 处理是将异常名抛出到方法声明上,是为了告诉调用者,你需要处理这个异常或这把它抛出去。
注意:
- 尽量不要在顶层方法(比如 main 方法) 中将异常抛出。
- 运行期异常抛出,调用者可以不进行处理
- 编译期异常进行抛出,调用者必须要处理
- throws 后可以跟多个异常名
VI. throw 关键字
1. throw
在功能方法中内部出现某种情况,程序不能继续运行,需要进行跳转时,就用 throw
把异常对象抛出,代码实例如下:
- 运行期异常
package org.lovian.exception;
public class ThrowDemo {
public static void main(String[] args) {
method();
}
public static void method(){
int a = 10;
int b = 0;
if(b == 0){
throw new ArithmeticException("b can't be 0"); // 抛出的其实是异常的对象
}else{
System.out.println(a/b);
}
}
}
result:
Exception in thread "main" java.lang.ArithmeticException: b can't be 0
at org.lovian.exception.ThrowDemo.method(ThrowDemo.java:13)
at org.lovian.exception.ThrowDemo.main(ThrowDemo.java:5)
- throw Exception 异常
package org.lovian.exception;
public class ThrowDemo {
public static void main(String[] args) {
try {
method();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void method() throws Exception{
int a = 10;
int b = 0;
if(b == 0){
throw new Exception(); // 抛出 Exception 则必须要处理, Exception 可能是编译期异常
}else{
System.out.println(a/b);
}
}
}
result:
java.lang.Exception: b can't be 0
at org.lovian.exception.ThrowDemo.method(ThrowDemo.java:18)
at org.lovian.exception.ThrowDemo.main(ThrowDemo.java:6)
2. throws 和 throw 的区别
- throws:
- 用在方法声明后, 跟的是异常类名
- 可以跟多个异常类名,用逗号隔开
- 表示抛出异常,由该方法的调用者来处理
- throws 表示出现异常的一种可能性,并不一定会发生这些异常
- throw:
- 用在方法体内,跟的是异常对象
- 只能抛出一个异常对象
- 表示抛出异常,由方法体内的语句处理
- throw是抛出了异常,执行throw则一定抛出了某种异常
VII. 怎样处理异常
- 原则:
- 如果该功能内部可以将问题处理,用 try/catch, 如果处理不了,交由调用者处理,用 throws
- 区别:
- 后续程序需要运行就用 try/catch,就是说 try/catch 之后的代码还会执行
- 后续程序不需要继续运行就用 throws,如果异常被抛出了,那么发生异常的地方之后的代码不会被执行
- 顶层调用方法一般用 try/catch 来处理
VIII. finally的特点与作用
finally:是 try…catch…finally 的一部分,参与处理异常
- finally的特点:
- 被 finally 控制的语句体一定会执行
- 特殊情况: 在执行到 finally 之前 jvm 退出了(比如 System.exit(0))
- finally的作用:
- 用于释放资源,在 IO 操作和数据库操作中会见到
- 特殊情况:
- 单独使用 try…finally, 不处理异常,目的是为了释放资源
问题:
- 如果 catch 语句块中有 return 语句, 那么 finally 的代码还会执行吗? 如果会,请问是在 return 前还是在 return 后?
- 会执行,在 return 之前
- 准确的说,应该是在 return 中
package org.lovian.exception;
public class FinallyDemo {
public static void main(String[] args) {
System.out.println(getInt());
}
public static int getInt(){
int a = 10;
try{
System.out.println(a/0);
a = 20;
} catch(ArithmeticException e){
a = 30;
return a;
} finally {
a = 40;
}
return a;
}
}
result:
30
为什么明明 finally 中的语句块在 catch 中的 return a;
语句执行之前已经执行 a = 40;
了,为什么这里结果是 30?
因为在 catch 语句中,return a;
在执行到这一步的时候,这里不是 return a, 而是 return 30;
这个返回路径就形成了,但是它又发现 catch 语句块后,还有 finally 语句块,所以就继续执行 finally 的内容,就是 a = 40
, 再次回到以前的返回路径,再此走return 30
IX. 自定义异常
有些时候,在工程上,我们需要定义自己的异常类,用来描述项目中的异常类型,而 Java 不可能提供所有的情况,所以这时候,我们需要自己定义异常:
- 继承 Exception 类
- 继承 RuntimeException 类
而自定义异常 MyException
时,一般可以不添加任何东西,完全继承于父类的成员方法,当然也可以重写这些方法,在使用的时候,可以通过条件判断语句来 throw new MyException()
示例代码如下:
package org.lovian.exception;
public class MyException extends Exception{
/**
*
*/
private static final long serialVersionUID = 1L;
public MyException() {
super();
}
public MyException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public MyException(String message, Throwable cause) {
super(message, cause);
}
public MyException(String message) {
super(message);
}
public MyException(Throwable cause) {
super(cause);
}
}
X. 异常注意事项
- 子类覆写父类方法时,子类的方法必须抛出相同的异常或父类异常的子类
- 如果父类抛出了多个异常,子类覆写父类时,只能抛出相同的异常或者是它的子集,子类不能抛出父类没有的异常
- 如果被覆写的方法没有异常的抛出,那么子类的方法绝对不可以抛出异常;如果子类方法内有异常发生,那么子类只能 try/catch , 不能 throws
Share this on