目录
一、异常介绍
1、什么是异常
程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。
2、Java
异常的分类
Java 中的所有异常类都继承于 Throwable 类,Throwable 主要包括两个大类: Error 类和 Exception 类
- 错误:Error 类及其子类的实例,代表了 JVM 本身的错误,包括虚拟机错误和线程死锁,一旦 Error 出现了,程序就彻底的挂了,并且程序一般不会从错误中恢复,被称为程序终结者,例如:JVM 内存溢出。
- 异常:Exception 类及其子类的实例,代表程序运行时发生的各种不期望发生的事件,可以被 Java 异常处理机制处理,是异常处理的核心。Exception 主要包括两大类,非检查异常(RuntimeException)和检查异常(其他的一些异常)
3、非检查异常(unckecked exception)
非检查异常也叫 Runtime 异常(运行时异常),所有 RuntimeException 类及其子类的实例都被称为非检查异常。javac
在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常。
对于这些异常,我们应该修正代码,而不是去通过异常处理器处理,这样的异常发生的原因多半是代码写的有问题,比如:除零异常(ArithmeticException)、强制类型转换异常(ClassCastException)、数组索引越界异常(ArrayIndexOutOfBoundsException)、空指针异常(NullPointerException)等等
4、检查异常(checked exception)
检查异常:不是 RuntimeException 类及其子类的异常实例被称为检查异常。javac
强制要求程序员为这样的异常做预备处理工作(使用try…catch…finally或者throws),在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。
这样的异常一般是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用他编写的程序,于是程序员就应该为这样的异常时刻准备着。如SQLException、IOException、ClassNotFoundException 等。
二、捕获异常
1、异常捕获的组成部分
- try 块:负责捕获异常,一旦try中发现异常,程序的控制权将被移交给catch块中的异常处理程序(try语句块不可以独立存在,必须与 catch 或者 finally 块同存);
- catch 块:如何处理?比如发出警告:提示、检查配置、网络连接,记录错误等。执行完catch块之后程序跳出catch块,继续执行后面的代码(处理多个 catch 块时,要按照先 catch 子类后 catch 父类的处理方式,因为会就近处理/由上自下异常)
- finally 块:最终执行的代码,用于关闭和释放资源。
在 Java 中,异常处理的任务就是将执行控制流从异常发生的地方转移到能够处理这种异常的地方去,也就是说:当一个函数的某条语句发生异常时,这条语句的后面的语句不会再执行,它失去了焦点。
当执行流跳转到最近的匹配的异常处理 catch 代码块去执行,异常被处理完后,执行流会接着在处理了这个异常的catch代码块后面接着执行,下面实例代码展示了这种处理逻辑:
public static void main(String[] args){
try {
foo();
}catch(ArithmeticException ae) {
System.out.println("处理异常");
}
}
public static void foo(){
int a = 5 / 0; // 异常抛出点
System.out.println("异常后面的代码"); // 出现异常后面的代码不会执行
}
2、多重捕获块
一个 try 代码块后面跟随多个 catch 代码块的情况就叫多重捕获,如下面的代码:
try {
file = new FileInputStream(fileName);
x = (byte) file.read();
} catch(FileNotFoundException f) {
f.printStackTrace();
return -1;
} catch(IOException i) {
i.printStackTrace();
return -1;
} finally {
// 处理最后的代码
}
如果 try 块代码发生异常,异常被抛给第一个 catch 块,如果异常匹配则执行第一个 catch 块中的代码,如果不匹配则传递给第二个 catch 块进行异常匹配,以此类推,知道异常被捕获或者通过所有的 catch 块。多重异常处理代码块顺序为:先子类再父类(顺序不对编译器会提醒错误),finally 语句块处理最终将要执行的代码。
三、throw
和 throws
关键字
1、throw
异常抛出语句
throw 的作用是:将产生的异常抛出,是手动抛出异常的一个动作。当手动抛出异常时,如果不使用 try catch 语句去尝试捕获这种异常,或者添加声明来将异常抛出给更上一层的调用者进行处理的话,程序将会在这里停止,并不会执行剩下的代码。
一般会用于程序出现某种逻辑时程序员主动抛出某种特定类型的异常,如:
public static void main(String[] args) {
String s = "abc";
if(s.equals("abc")) {
throw new NumberFormatException();
} else {
System.out.println(s);
}
}
>>>>>
Exception in thread "main" java.lang.NumberFormatException
2、throws
函数声明
throws 的作用是声明将要抛出何种类型的异常。当某个方法可能会抛出某种异常时,用 throws 声明可能抛出的异常,然后交给上层调用它的方法程序处理,如:
// 声明可能会抛出 NumberFormatException 异常
public static void function() throws NumberFormatException{
String s = "abc";
System.out.println(Double.parseDouble(s));
}
public static void main(String[] args) {
try {
function();
} catch (NumberFormatException e) { // 捕获 function() 可能抛出的异常
System.err.println("非数据类型不能转换。");
}
}
3、throw
与 throws
的比较
- throw 出现在函数体,而 throws 出现在方法函数头
- throw 是直接抛出异常,执行 throw 则一定发生了某种异常,而 throws 表示出现异常的一种可能性,并不一定会发生这些异常
- 两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。
注意:如果某个方法调用了抛出异常的方法,那么必须添加 try catch 语句去尝试捕获这种异常, 或者添加声明,将异常抛出给更上一层的调用者进行处理。
四、finally
块和 return
首先我们需要知道:在 try 块中即便有 return,break,continue 等改变执行流的语句,finally 块代码也会执行,比如:
public static void main(String[] args)
{
try{
return 5;
} finally{
System.out.println("执行 finally");
}
}
>>>>>
执行 finally
finally 中的 return 会覆盖 try 或者 catch 中的返回值:
public static void main(String[] args) {
int re1 = testException1();
System.out.println(re1);
int re2 = testException2();
System.out.println(re2);
}
public static int testException1() {
try {
return 1;
} finally {
return 2;
}
}
public static int testException2() {
try {
int a = 5 / 0;
} catch (ArithmeticException e) {
return 2;
} finally {
return 3;
}
}
>>>>>
2
3
finally 中的 return 会抑制前面 try 或者 catch 块中的异常:
public class TestException {
public static void main(String[] args){
try{
System.out.println(foo()); // 输出100
} catch (Exception e){
System.out.println(e.getMessage()); // 没有捕获到异常
}
try{
System.out.println(bar()); // 输出100
} catch (Exception e){
System.out.println(e.getMessage()); // 没有捕获到异常
}
}
// catch 中的异常被抑制
public static int foo() throws Exception{
try {
int a = 5 / 0;
return 1;
}catch(ArithmeticException amExp) {
throw new Exception("我将被忽略,因为下面的finally中使用了return");
}finally {
return 100;
}
}
// try 中的异常被抑制
public static int bar() throws Exception{
try {
int a = 5 / 0;
return 1;
}finally {
return 100;
}
}
}
finally 中的异常会覆盖前面 try 或者 catch 中的异常:
class TestException {
public static void main(String[] args){
try{
foo();
} catch (Exception e){
System.out.println(e.getMessage()); // 输出:我是finaly中的Exception
}
try{
bar();
} catch (Exception e){
System.out.println(e.getMessage()); // 输出:我是finaly中的Exception
}
}
// catch 中的异常被抑制
public static void foo() throws Exception{
try {
int a = 5 / 0;
}catch(ArithmeticException amExp) {
throw new Exception("我将被忽略,因为下面的finally中抛出了新的异常");
}finally {
throw new Exception("我是finaly中的Exception");
}
}
// try 中的异常被抑制
public static void bar() throws Exception{
try {
int a = 5 / 0;
}finally {
throw new Exception("我是finaly中的Exception");
}
}
}
这三种情况都是不好的编程习惯,所以我们建议:
- 不要在 fianlly 中使用 return;
- 不要在 finally 中抛出异常;
- 减轻 finally 的任务,不要在 finally 中做一些其它的事情,finally 块仅仅用来释放资源是最合适的;
- 尽量将所有的 return 写在函数的最后面,而不是在
try...catch...finally
中。
五、自定义异常
1、为什么要使用自定义异常
- 我们在工作的时候,项目是分模块或者分功能开发的,基本不会你一个人开发一整个项目,使用自定义异常类就统一了异常对外展示的方式;
- 自定义异常可以在我们项目中处理某些特殊的业务逻辑时抛出异常,而这种异常是 Java 没有的,换句话说,系统中有些错误是符合 Java 语法的,但不符合我们项目的业务逻辑;
- 有时候我们遇到某些校验或者问题时,需要直接结束掉当前的请求,这时便可以通过抛出自定义异常来结束,如果你项目中使用了 SpringMVC 比较新的版本的话会有控制器增强,可以通过
@ControllerAdvice
注解写一个控制器增强类来拦截自定义的异常并响应给前端相应的信息。
2、自定义异常的方式
自定义异常需要注意以下几点:
- 所有异常都必须是 Throwable 的子类;
- 如果希望写一个检查性异常类,则需要继承 Exception 类;
- 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类;
下面我看一个自定义异常例子:
// 自定义一个检查性异常类
public class MyException extends Exception {
// 错误编码
private String errorCode;
public MyException(String message){
super(message);
}
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public static void main(String[] args) {
try {
throw new MyException("抛出自定义异常");
} catch(MyException e) {
e.printStackTrace();
}
}
}
>>>>>
com.my.main.MyException: 抛出自定义异常
at com.my.main.MyException.main(MyException.java:23)