异常在我们的生活中就是一些跟往常不一样的,不太正常的现象。在Java中,异常就是指程序出现了一些错误,也就是说程序没有按照我们想要的方向走。就比如说:我们写的一个模块传入的数据不符合你的要求;你访问在线的服务但是你的网络连接败;你查询数据库的数据为空;你访问的文件不存在或者文件的格式跟你所想的不一样;程序在运行的时候JVM出错了或者内存空间用完了等等。这些异常发生在程序运行期间,中断了正在执行程序的正常指令流。
1.1、异常体系结构 在Java中,所有的异常都是对象,他们有一个共同点父类就是Throwable,继承关系图如下
Throwable是所有异常类的祖先,它包括两个重要的子类:Error与Exception。
1.2、异常分类Error类
Error类及其子类用来描述Java运行系统中的内部错误以及资源耗尽错误,这种错误是程序无法处理的,并且比较严重。这种错误的出现一般与编码人员无关,而且发生这种错误JVM一般选择线程终止,因为这种情况在程序的处理范围外,仅凭程序本身是无法处理的,Java程序也不会对Error异常进行捕获和抛出。
Exception类
Exception异常可以通过捕获来使程序继续执行,是程序本身可以处理的异常,也称为非致命性异常。
根据错误发生原因可以分为运行时异常(RuntimeException)和检查性异常(CheckedException)
- 运行时异常:RuntimeException类及其子类异常。如:错误的类型转换、数组访问越界、访问null指针等。此类异常一般由程序的逻辑错误引起的,程序应该从逻辑角度尽可能避免此类异常的发生。这种异常可以选择捕获处理,也可以选择不处理。这类异常Java编译器不会检查它,也就是说,当程序出现了运行异常,也会编译通过。可以说,如果出现RuntimeException异常,那么一定是你的问题。
- 检查异常:所有的非运行异常都可以归为检查异常。如:试图在文件尾部后面读取数据、试图打开一个不存在的文件、试图根据给定的字符串查找 Class对象, 而这个字符串表示的类并不存在。这类异常是程序猿无法预见的。例如我们要打开一个不存在的文件,一个异常就发生了,这些异常在编译时不能被忽略,没有处理的检查异常,该程序就无法编译成功。
人们在遇到错误时会感觉不爽。如果一个用户在运行程序期间,由于程序的错误或一些外部环境的影响造成用户数据的丢失,用户就有可能不再使用这个程序了,为了避免这类事情的发生,至少应该做到以下几点:
- 向用户通告错误;
- 保存所有的工作结果;
- 允许用户以妥善的形式退出程序。
对于异常情况,例如,可能造成程序崩溃的错误输入,Java使用一种称为异常处理(exception handing)的错误捕获机制处理。
在 Java 应用程序中,异常处理机制为:抛出异常,捕捉异常。
2.1、抛出异常 当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。
抛出异常的方法:throws和throw
- throws: 声明方法可能抛出的异常,多个异常用,隔开。throws关键字将对象抛给上一级,如果不想处理可以继续向上抛出,但最终要有能够处理该异常的操作。语法格式:方法名(参数) throws 异常1, 异常2…异常n。
- throw: 用在方法体中或者抛出自定义的异常,程序在执行到throw语句时立刻停止,如果要捕获throw抛出的异常,则必须使用try-catch语句。语法格式 throw 异常对象。
例:throws
public class Main {
public static void popException() throws ArrayIndexOutOfBoundsException{
int[] arr = new int[1];
arr[2] = 8;
}
public static void main(String[] args) {
try {
popException();
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组越界异常!");
}
}
}
例: throw
public class Main {
public static void popException(int value) {
if (value == 0) {
throw new ArithmeticException("value不能为零!");
}
}
public static void main(String[] args) {
try {
popException(0);
} catch (ArithmeticException e) {
System.out.println(e.getMessage());
}
}
}
注意:
- throw语句后不允许跟其他语句,因为这些语句没有机会执行。
- 如果一个方法调用了另一个用throws声明抛出的异常,那么这个方法要么处理异常,要么继续抛出。
- throw语句抛出异常的两种情况:
- 当throw语句抛出的异常是Checked异常,则该throw语句要么处于try块里显式捕获该异常,要么放在一个带throws声明抛出的方法中,即把异常交给方法的调用者处理。
- 当throw语句抛出的异常是Runtime异常,则该语句无须放在try块内,也无须放在带throws声明抛出的方法中,程序既可以显式使用try…catch来捕获并处理该异常,也可以完全不理会该异常,把该异常交给方法的调用者处理。
我们已经知道了通过throw或者throws来抛出异常,同时在上面的例子中也简单说明了如何捕获异常,就是通过try..catch语句来捕获异常。
如果没有捕获异常操作,一旦发生了异常控制台就会打印异常类型及其堆栈轨迹。如下:
public class Main {
public static void popException(int value) {
if (value == 0) {
throw new ArithmeticException("value不能为零!");
}
}
public static void main(String[] args) {
popException(0);
}
}
所以在我们调试程序的时候就可与查看这个堆栈轨迹来找出我们错误发生的具体位置!
2.2.1、try…catch语句 下面是一个简单的try..catch语句:
public static void main(String[] args) {
try {
popException(0);
} catch (ArithmeticException e) {//捕获异常
//处理ArithmeticException异常
}
}
关于try..catch语句的特点:
- 如果try语句块中的任何代码抛出了catch字句中指定的一个异常类,那么程序跳过try语句块其余代码,然后跳转到catch语句执行对该类异常的处理。
- 如果try语句块中没有抛出异常,则跳过catch子句。
- 如果try语句块中的代码跑出来了catch语句中没有声明的异常那么该方法就立即退出(默认抛出异常给调用者处理)。
例:
public class Main {
public static void popException(int value) throws ArithmeticException{
int i = 4;
while (value >= 0) {
System.out.println("结果" + i / value--);
}
}
public static void main(String[] args) {
try {
popException(2);
System.out.println("代码执行12行了!");
} catch (ArithmeticException e) {
System.out.println("捕获异常除数为零!");
}
}
}
2.2.2、finally语句
代码一旦抛出一个异常,就会停止该方法的剩余代码,并推出这个方法。如果该方法在抛出异常前已经获取了只有它自己知道的一些本地资源,这些资源必须清理,这时就产生了问题。
一种解决方案就是我们先捕获异常,清理资源后再次抛出异常,但是这种方法比较繁琐,因为我们在异常发生的时候要清理资源,在异常不发生也清理资源,等于同样的操作我们代码写了两遍。在java中我们可以使用finally实现上述操作。
不管是否有异常被捕获,finally中的语句一定会执行。一般写法如下(用文件输入流举例):
try {
FileInputStream in = new FileInputStream(...);
//对文件输入流进行操作
} catch (IOException e) {
//处理异常
} finally {
//关闭文件输入流
in.close();
}
finally特点:
- 无论是否发生异常,finally语句一定会执行,如果在finally语句中放入了return语句,则try以及catch中的return语句全部失效,但是我们一般不这样做。
- finally语句遇见下述情况不会执行
- 在执行finally语句之前用System.exit()退出程序。
- finally语句中发生异常。
- 程序所在的线程死亡。
- CPU关闭。
- try、catch和finally都不能单独使用,只能是try-catch、try-finally或者try-catch-finally。
下面的例子让大家对try..catch..finally语句加深印象:
public class Main {
public Main() {
}
boolean testEx() throws Exception {
boolean ret = true;
try {
ret = testEx1();
} catch (Exception e) {
System.out.println("testEx, catch exception");
ret = false;
throw e;
} finally {
System.out.println("testEx, finally; return value=" + ret);
return ret;
}
}
boolean testEx1() throws Exception {
boolean ret = true;
try {
ret = testEx2();
if (!ret) {
return false;
}
System.out.println("testEx1, at the end of try");
return ret;
} catch (Exception e) {
System.out.println("testEx1, catch exception");
ret = false;
throw e;
} finally {
System.out.println("testEx1, finally; return value=" + ret);
return ret;
}
}
boolean testEx2() throws Exception {
boolean ret = true;
try {
int b = 12;
int c;
for (int i = 2; i >= -2; i--) {
c = b / i;
System.out.println("i=" + i);
}
return true;
} catch (Exception e) {
System.out.println("testEx2, catch exception");
ret = false;
throw e;
} finally {
System.out.println("testEx2, finally; return value=" + ret);
return ret;
}
}
public static void main(String[] args) {
Main main = new Main();
try {
main.testEx();
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.2.3、try…with…Resources语句
在java 7之后出现了try…with…Resources语句,也就是带资源的try语句。下面是简单的try…with…Resources语句:
try (Resource res = ...) {
//res的使用
}
简单地说就是在try执行完之后会自动调用`res.close()`。如果在使用资源的时候尽量使用这种方式减少代码量。2.3、自定义异常
我们在开发的时候会遇到很多标准异常类无法描述的异常。在这种情况下创建自己的异常类就很重要了。我们创建的时候只需要创建一个继承Exception的类或者其子类。一般自定义的异常类包含两个构造器,一个是默认的构造器,另一个是包含详细信息的构造器。例如:
//判断长度是否合法的自定义异常
public class LengthException extends Exception {
public LengthException() {}
public LengthException(String s) {
super(s);
}
@Override
public String getMessage() {
return super.getMessage();
}
}
接下来就和之前抛出异常一样了。
2.4、使用异常的技巧- 异常处理不能代表简单的测试。
- 不要过分的细分异常。
- 充分利用异常层次结构**(尽量使用准确的异常类描述)**。
- 不要压制**(捕获不处理)**异常。
- 在检测错误时,“苛刻”要比放任好。
- 不要羞于传递异常。
https://blog.csdn.net/zx64881926/article/details/52300271 Java核心技术 卷一



