异常:就是出现的问题。
在Java中异常被当成对象进行处理,所有的异常类都继承于Throwable类,如果Java提供的异常类并不能满足需求,用户还可以自己定义一个异常类。
下面是异常体系结构:
Throwable又分成了Error和Exception。本文仅讨论Exception及其子类,因为Error出现的话也不是我们能够处理的。
Exception又分为RuntimeException和其他异常。
由程序逻辑错误导致的异常属于RuntimeException体系异常,也叫不受查(unchecked)异常,并不需要进行异常处理,当然也可以处理并不会报错。比如以下几种常见的异常:
①错误的类型转换;
②数组索引越界;
③访问空指针。
而程序本身没有其他问题,由于像IO这类问题导致的异常属于其他异常,也叫受查(checked)异常,必须显示地进行异常处理(捕获或者抛出异常),否则会报错。比如以下这种情况:
①读取本地不存在的文件。
②网络断开连接。
当出现异常时有两种解决方式:
1、捕获异常进行处理,即try - catch捕获异常。
2、将异常传递给方法调用者,即抛出异常。
如果调用了一个抛出异常的方法,那么必须进行处理或传递,如何选择呢?
如果知道如何处理的异常可以进行捕获,而那些不知道怎样处理的异常继续向上抛出。
向上抛出
什么时候需要向上抛出异常?
(1)传递一个危险信号,需要让调用者知道;
(2)本方法没有处理异常的能力,方法调用者有能力处理;
(3)抛出是框架层面的选择。
这里介绍一下JVM默认的异常处理方式:
①结束程序的运行,即异常下面的代码就不会执行了;
②将异常信息、异常原因和异常位置以红色字体输出在控制台。
什么是抛出异常?
1、throws
- 在方法的后面使用throws关键字将可能会抛出的异常进行声明,表示此方法不处理异常,一旦发生异常会将异常进行抛出,交由方法的调用处进行处理。
具体格式如下:
public Image loadImage(String s) throws IOException {
}
2、throw
- 使用关键字 throw 手动抛出一个异常,即手动进行异常类对象的实例化操作。
throw new EOFException();
关于throws和throw两个的细节:
- 都是抛出异常,可同时使用,也可单独使用;
- 都将异常传递给方法的调用者;
- 都会结束当前方法中剩余代码的执行。
捕获异常
使用try-catch语句。捕获单个异常格式如下:
try {
编写可能会出现异常的代码
} catch (异常类型 e)
{
处理异常的代码 //记录日志/打印异常信息/继续抛出异常
}
在一个try语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。可以按照下列方式为每个异常类型使用一个单独的catch子句:
try{
编写可能会出现异常的代码
}catch(FileNotFoundException e) { //当try中出现FileNotFoundException 类型异常,就用该catch来捕获
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}catch(UnknownHostException e) { //当try中出现UnknownHostException 类型异常,就用该catch来捕获
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
注意多个catch语句中的异常类型不能一样,并且父类的异常应该放在子类的后面,否则会报错,因为如果写在最前面的话,异常在第一个岔路口就被抓住了,后面的捕获就多余了,依据Java的严谨性,不允许出现这种情况。
而且异常对象可能包含与异常本身有关的信息。要想获得对象的更多信息,可以试着使用e.getMessage()或者e.printStackTrace()得到详细的错误信息(如果有的话),或者使用 e.getClass().getName() 得到异常对象的实际类型。
在Java SE7中,同一个 catch 子句中可以捕获多个异常类型,中间用 | 进行隔开。例如,假设对应缺少文件和未知主机异常的动作是一样的,就可以合并catch子句:
try {
编写可能会出现异常的代码
} catch (FileNotFoundException| UnknownHostException e) {
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
} catch(I0Exception e) {
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
那么什么时候抛出异常?什么时候捕获异常呢?
解释1
一句话回答就是“没有金刚钻,不揽瓷器活”。
如果你发现了某个问题,你又没能力解决,你就该汇报给上级,这就是【抛出异常】。
如果你接到了下属汇报的问题,你也没能力解决,你就只好再汇报给你的上级,这就是【继续抛出】。
只有你有能力解决的情况下,你才可以把这个问题解决掉,这就是【捕获异常】。
总结就是:谁遇到困难,谁抛出异常。谁解决问题,谁捕获异常。
下层抛出上层进行捕获。
细节1:
如果一个异常没有在任何地方被捕获,那么就会由虚拟机调用默认的异常处理机制,结束程序的运行,并在控制台打印异常有关信息。
前面还没懂吗,你不用throw抛出异常,调用者就接收不到异常,就无法捕获异常并处理,最后会交给虚拟机处理,这样程序就会停止运行,之后的代码无法运行。
给出一个代码例子:
public class test05 {
public static void main(String[] args) {
int[] arr = null;
try {
int max = getMax(arr);
} catch (NullPointerException e) {
System.out.println("空指针异常");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("索引越界异常");
}
}
public static int getMax (int[] arr) throws NullPointerException, ArrayIndexOutOfBoundsException{
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
}
finally
不论是否有异常被捕获,finally子句都将被执行。比如打开了一些资源,但是异常发生时,希望这些文件可以关闭,就可以使用finally。下面举一个例子,其中的1,2...表示代码:
InputStream in = new FileInputStream(...);
try {
// 1
code that throw exceptions
// 2
} catch {
// 3
show error message
// 4
} finally {
// 5
in.close();
}
// 6
1、try语句中是否有异常发生?
①try语句中没有异常发生。那么try语句中的所有代码都会被执行,然后执行finally语句中的所有代码,接着执行finally后面的代码,即执行标注的1,2,5,6。
2、try语句中有异常发生,但是catch语句是否可以捕获?
(1)try语句中发生一个可以由catch语句捕获的异常,而catch语句中是否又抛出了异常?
①catch语句中没有抛出了异常。首先会执行try语句中异常发生之前的代码,然后执行catch语句中的所有代码,接着是finally语句中的所有代码,最后执行finally后面的代码,即执行标注的1,3,4,5,6。
②catch语句抛出了异常。还会执行finally子句吗?必须执行,要不然finally还有什么用呢,就是不会实现finally子句后面的语句了,结束方法的运行。即1,3,5。
警告:当 finally 子句包含return语句时,将会出现一种意想不到的结果。假设使用return语句从try语句块中退出。在方法返回前,finally子句的内容将被执行。如果 finally 子句中也有一个return语句,这个返回值将会覆盖原始的返回值。下面是一个例子:
public static int f(int n) {
try {
int r= n* n;
return r;
}
finally {
if(n == 2)
return 0;
}
}
如果调用f(2),那么 try语句块的计算结果为r=4,并执行return 语句。然而,在方法真正返回前,还要执行 finally 子句。finally 子句将使得方法返回 0,这个返回值覆盖了原始的返回值 4。
总结就是:假如try和finally子句中都带有return返回值,最终会返回finally子句中的返回值。