2023年11月29日发(作者:)

Java异常

1. Throwable

Throwable Java 语⾔中所有错误(Error)和异常(Exception)的超类。

Throwable 包含了其线程创建时线程执⾏堆栈的快照,它提供了 printStackTrace() 等接⼝⽤于获取堆栈跟踪数据等信息。

主要⽅法:

fillInStackTrace - ⽤当前的调⽤栈层次填充 Throwable 对象栈层次,添加到栈层次任何先前信息中。

getMessage - 返回关于发⽣的异常的详细信息。这个消息在 Throwable 类的构造函数中初始化了。

getCause - 返回⼀个 Throwable 对象代表异常原因。

getStackTrace - 返回⼀个包含堆栈层次的数组。下标为 0 的元素代表栈顶,最后⼀个元素代表⽅法调⽤堆栈的栈底。

printStackTrace - 打印 toString() 结果和栈层次到 ,即错误输出流。

toString - 使⽤ getMessage 的结果返回代表 Throwable 对象的字符串。

2. Error

Error Throwable 的⼀个⼦类。Error 表⽰合理的应⽤程序不应该尝试捕获的严重问题。⼤多数此类错误都是异常情况。编译器不会检查

Error

常见 Error

AssertionError - 断⾔错误。

VirtualMachineError - 虚拟机错误。

UnsupportedClassVersionError - Java 类版本错误。

StackOverflowError - 栈溢出错误。

OutOfMemoryError - 内存溢出错误。

3. Exception

Exception Throwable 的⼀个⼦类。Exception 表⽰合理的应⽤程序可能想要捕获的条件。

编译器会检查 Exception 异常。此类异常,要么通过 throws 进⾏声明抛出,要么通过 try catch 进⾏捕获处理,否则不能通过编译。

常见 Exception

ClassNotFoundException - 应⽤程序试图加载类时,找不到相应的类,抛出该异常。

CloneNotSupportedException - 当调⽤ Object 类中的 clone ⽅法克隆对象,但该对象的类⽆法实现 Cloneable 接⼝时,抛出该异常。

IllegalAccessException - 拒绝访问⼀个类的时候,抛出该异常。

InstantiationException - 当试图使⽤ Class 类中的 newInstance ⽅法创建⼀个类的实例,⽽指定的类对象因为是⼀个接⼝或是⼀个抽象

类⽽⽆法实例化时,抛出该异常。

InterruptedException - ⼀个线程被另⼀个线程中断,抛出该异常。

NoSuchFieldException - 请求的变量不存在。

NoSuchMethodException - 请求的⽅法不存在。

⽰例:

public class ExceptionDemo {

public static void main(String[] args) {

Method method = String.class.getMethod("toString", int.class);

}

};

试图编译运⾏时会报错:

Error:(7, 47) java: 未报告的异常错误MethodException; 必须对其进⾏捕获或声明以便抛出

4. RuntimeException

RuntimeException Exception 的⼀个⼦类。RuntimeException 是那些可能在 Java 虚拟机正常运⾏期间抛出的异常的超类。

编译器不会检查 RuntimeException 异常。当程序中可能出现这类异常时,倘若既没有通过 throws 声明抛出它,也没有⽤ try catch 语句捕

获它,程序还是会编译通过。

⽰例:

public class RuntimeExceptionDemo {

public static void main(String[] args) {

// 此处产⽣了异常

int result = 10 / 0;

n("两个数字相除的结果:" + result);

n("----------------------------");

}

};

运⾏时输出:

Exception in thread "main" eticException: / by zero

at (:6)

常见 RuntimeException

ArrayIndexOutOfBoundsException - ⽤⾮法索引访问数组时抛出的异常。如果索引为负或⼤于等于数组⼤⼩,则该索引为⾮法索引。

ArrayStoreException - 试图将错误类型的对象存储到⼀个对象数组时抛出的异常。

ClassCastException - 当试图将对象强制转换为不是实例的⼦类时,抛出该异常。

IllegalArgumentException - 抛出的异常表明向⽅法传递了⼀个不合法或不正确的参数。

IllegalMonitorStateException - 抛出的异常表明某⼀线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器⽽本⾝

没有指定监视器的线程。

IllegalStateException - 在⾮法或不适当的时间调⽤⽅法时产⽣的信号。换句话说,即 Java 环境或 Java 应⽤程序没有处于请求操作所

要求的适当状态下。

IllegalThreadStateException - 线程没有处于请求操作所要求的适当状态时抛出的异常。

IndexOutOfBoundsException - 指⽰某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。

NegativeArraySizeException - 如果应⽤程序试图创建⼤⼩为负的数组,则抛出该异常。

NullPointerException - 当应⽤程序试图在需要对象的地⽅使⽤ null 时,抛出该异常

NumberFormatException - 当应⽤程序试图将字符串转换成⼀种数值类型,但该字符串不能转换为适当格式时,抛出该异常。

SecurityException - 由安全管理器抛出的异常,指⽰存在安全侵犯。

StringIndexOutOfBoundsException - 此异常由 String ⽅法抛出,指⽰索引或者为负,或者超出字符串的⼤⼩。

UnsupportedOperationException - 当不⽀持请求的操作时,抛出该异常。

⾃定义⼀个异常类,只需要继承 Exception RuntimeException 即可。

⽰例:

public class MyExceptionDemo {

public static void main(String[] args) {

throw new MyException("⾃定义异常");

}

static class MyException extends RuntimeException {

public MyException(String message) {

super(message);

}

}

}

输出:

Exception in thread "main" ptionDemo$MyException: ⾃定义异常

at (:9)

如果想在程序中明确地抛出异常,需要⽤到 throw throws

如果⼀个⽅法没有捕获⼀个检查性异常,那么该⽅法必须使⽤ throws 关键字来声明。throws 关键字放在⽅法签名的尾部。

throw⽰例:

public class ThrowDemo {

public static void f() {

try {

throw new RuntimeException("抛出⼀个异常");

} catch (Exception e) {

n(e);

}

}

public static void main(String[] args) {

f();

}

};

也可以使⽤ throws 关键字抛出⼀个异常,⽆论它是新实例化的还是刚捕获到的。

throws⽰例:

public class ThrowsDemo {

public static void f1() throws NoSuchMethodException, NoSuchFieldException {

Field field = Integer.class.getDeclaredField("digits");

if (field != null) {

n("反射获取 digits ⽅法成功");

}

Method method = String.class.getMethod("toString", int.class);

if (method != null) {

n("反射获取 toString ⽅法成功");

}

}

public static void f2() {

try {

// 调⽤ f1 处,如果不⽤ try catch ,编译时会报错

f1();

} catch (NoSuchMethodException e) {

tackTrace();

} catch (NoSuchFieldException e) {

tackTrace();

}

}

public static void main(String[] args) {

f2();

}

};

throw throws 的区别:

throws 使⽤在函数上,throw 使⽤在函数内。

throws 后⾯跟异常类,可以跟多个,⽤逗号区别;throw 后⾯跟的是异常对象。

使⽤ try catch 关键字可以捕获异常。try catch 代码块放在异常可能发⽣的地⽅。

它的语法形式如下:

try {

// 可能会发⽣异常的代码块

} catch (Exception e1) {

// 捕获并处理try抛出的异常类型Exception

} catch (Exception2 e2) {

// 捕获并处理try抛出的异常类型Exception2

} finally {

// ⽆论是否发⽣异常,都将执⾏的代码块

}

此外,JDK7 以后,catch 多种异常时,也可以像下⾯这样简化代码:

try {

// 可能会发⽣异常的代码块

} catch (Exception | Exception2 e) {

// 捕获并处理try抛出的异常类型

} finally {

// ⽆论是否发⽣异常,都将执⾏的代码块

}

try try 语句⽤于监听。将要被监听的代码(可能抛出异常的代码)放在 try 语句块之内,当 try 语句块内发⽣异常时,异常就被抛出。

catch catch 语句包含要捕获异常类型的声明。当保护代码块中发⽣⼀个异常时,try 后⾯的 catch 块就会被检查。

finallyfinally 语句块总是会被执⾏,⽆论是否出现异常。try catch 语句后不⼀定⾮要finally 语句。finally 常⽤于这样的场景:由于

finally 语句块总是会被执⾏,所以那些在 try 代码块中打开的,并且必须回收的物理资源(如数据库连接、⽹络连接和⽂件),⼀般会放在

finally 语句块中释放资源。

trycatchfinally 三个代码块中的局部变量不可共享使⽤。

catch 块尝试捕获异常时,是按照 catch 块的声明顺序从上往下寻找的,⼀旦匹配,就不会再向下执⾏。因此,如果同⼀个 try 块下的多

catch 异常类型有⽗⼦关系,应该将⼦类异常放在前⾯,⽗类异常放在后⾯。

其实finally也并不总是会运⾏的,当在trycatch语句中调⽤(status); finally语句就不会运⾏。它表⽰退出当前运⾏的虚拟

机,status为任意值, 0是正常退出,⾮0是⾮正常退出。

⽰例:

public class TryCatchFinallyDemo {

public static void main(String[] args) {

try {

// 此处产⽣了异常

int temp = 10 / 0;

n("两个数字相除的结果:" + temp);

n("----------------------------");

} catch (ArithmeticException e) {

n("出现异常了:" + e);

} finally {

n("不管是否出现异常,都执⾏此代码");

}

}

};

异常链是以⼀个异常对象为参数构造新的异常对象,新的异常对象将包含先前异常的信息。

通过使⽤异常链,我们可以提⾼代码的可理解性、系统的可维护性和友好性。

我们有两种⽅式处理异常,⼀是 throws 抛出交给上级处理,⼆是 try…catch 做具体处理。try…catch catch 块我们可以不需要做任何处

理,仅仅只⽤ throw 这个关键字将我们封装异常信息主动抛出来。然后在通过关键字 throws 继续抛出该⽅法异常。它的上层也可以做这样

的处理,以此类推就会产⽣⼀条由异常构成的异常链。

⽰例:

public class ExceptionChainDemo {

static class MyException1 extends Exception {

public MyException1(String message) {

super(message);

}

}

static class MyException2 extends Exception {

public MyException2(String message, Throwable cause) {

super(message, cause);

}

}

public static void f1() throws MyException1 {

throw new MyException1("出现 MyException1");

}

public static void f2() throws MyException2 {

try {

f1();

} catch (MyException1 e) {

throw new MyException2("出现 MyException2", e);

}

}

public static void main(String[] args) throws MyException2 {

f2();

}

}

1. finally 覆盖异常

Java 异常处理中 finally 中的 return 会覆盖 catch 代码块中的 return 语句和 throw 语句,所以 Java 不建议在 finally 中使⽤ return 语句。

此外 finally 中的 throw 语句也会覆盖 catch 代码块中的 return 语句和 throw 语句。

⽰例:

public class FinallyOverrideExceptionDemo {

static void f() throws Exception {

try {

throw new Exception("A");

} catch (Exception e) {

throw new Exception("B");

} finally {

throw new Exception("C");

}

}

public static void main(String[] args) {

try {

f();

} catch (Exception e) {

n(sage());

}

}

}

输出:C

2. 覆盖抛出异常的⽅法

当⼦类重写⽗类带有 throws 声明的函数时,其 throws 声明的异常必须在⽗类异常的可控范围内——⽤于处理⽗类的 throws ⽅法的异常处

理器,必须也适⽤于⼦类的这个带 throws ⽅法 。这是为了⽀持多态。

⽰例:

public class ExceptionOverrideDemo {

static class Father {

public void start() throws IOException {

throw new IOException();

}

}

static class Son extends Father {

@Override

public void start() throws SQLException {

throw new SQLException();

}

}

public static void main(String[] args) {

Father obj1 = new Father();

Father obj2 = new Son();

try {

();

();

} catch (IOException e) {

tackTrace();

}

}

}

上⾯的⽰例编译时会报错,原因在于:

因为 Son 类抛出异常的实质是 SQLException,⽽ IOException ⽆法处理它。那么这⾥的 try catch 就不能处理 Son 中的异常了。多态就

不能实现了。

3. 异常和线程

如果 Java 程序只有⼀个线程,那么没有被任何代码处理的异常会导致程序终⽌。如果 Java 程序是多线程的,那么没有被任何代码处理的异

常仅仅会导致异常所在的线程结束。

4. 最佳实践

对可恢复的情况使⽤检查性异常(Exception),对编程错误使⽤运⾏时异常(RuntimeException

优先使⽤ Java 标准的异常

抛出与抽象相对应的异常

在细节消息中包含能捕获失败的信息

尽可能减少 try 代码块的⼤⼩

尽量缩⼩异常范围。例如,如果明知尝试捕获的是⼀个 ArithmeticException,就应该 catch ArithmeticException,⽽不是 catch 范围较

⼤的 RuntimeException,甚⾄是 Exception

尽量不要在 finally 块抛出异常或者返回值

不要忽略异常,⼀旦捕获异常,就应该处理,⽽⾮丢弃

异常处理效率很低,所以不要⽤异常进⾏业务逻辑处理

各类异常必须要有单独的⽇志记录,将异常分级,分类管理,因为有的时候仅仅想给第三⽅运维看到逻辑异常,⽽不是更细节的信息。

如何对异常进⾏分类

逻辑异常,这类异常⽤于描述业务⽆法按照预期的情况处理下去,属于⽤户制造的意外。

代码错误,这类异常⽤于描述开发的代码错误,例如 NPEILLARG,都属于程序员制造的 BUG

专有异常,多⽤于特定业务场景,⽤于描述指定作业出现意外情况⽆法预先处理。

参考