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 块就会被检查。
finally:finally 语句块总是会被执⾏,⽆论是否出现异常。try catch 语句后不⼀定⾮要finally 语句。finally 常⽤于这样的场景:由于
finally 语句块总是会被执⾏,所以那些在 try 代码块中打开的,并且必须回收的物理资源(如数据库连接、⽹络连接和⽂件),⼀般会放在
finally 语句块中释放资源。
try、catch、finally 三个代码块中的局部变量不可共享使⽤。
catch 块尝试捕获异常时,是按照 catch 块的声明顺序从上往下寻找的,⼀旦匹配,就不会再向下执⾏。因此,如果同⼀个 try 块下的多
个 catch 异常类型有⽗⼦关系,应该将⼦类异常放在前⾯,⽗类异常放在后⾯。
其实finally也并不总是会运⾏的,当在try或catch语句中调⽤(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 块抛出异常或者返回值
不要忽略异常,⼀旦捕获异常,就应该处理,⽽⾮丢弃
异常处理效率很低,所以不要⽤异常进⾏业务逻辑处理
各类异常必须要有单独的⽇志记录,将异常分级,分类管理,因为有的时候仅仅想给第三⽅运维看到逻辑异常,⽽不是更细节的信息。
如何对异常进⾏分类
逻辑异常,这类异常⽤于描述业务⽆法按照预期的情况处理下去,属于⽤户制造的意外。
代码错误,这类异常⽤于描述开发的代码错误,例如 NPE,ILLARG,都属于程序员制造的 BUG。
专有异常,多⽤于特定业务场景,⽤于描述指定作业出现意外情况⽆法预先处理。
参考


发布评论