异常

1.异常

Java 中的异常处理是处理运行时错误以保持应用程序正常流程的有效手段之一。Java异常处理是一种处理运行时错误的机制,如ClassNotFoundException、IOException、SQLException、RemoteException等。

异常是不需要的或意外的事件,它发生在程序执行期间,即在运行时,它会扰乱程序指令的正常流程。程序可以捕获和处理异常。当方法中发生异常时,它会创建一个对象。该对象称为异常对象。它包含有关异常的信息,例如异常的名称和描述以及发生异常时程序的状态。

错误表示不可恢复的情况,例如 Java 虚拟机 (JVM) 内存耗尽、内存泄漏、堆栈溢出错误、库不兼容、无限递归等。错误通常是程序员无法控制的,我们不应该试图去处理错误。

讨论最重要的部分,即Error 和 Exception 之间的区别,如下所示:

  • 错误: 错误表示一个合理的应用程序不应尝试捕获的严重问题。
  • 异常: 异常表示合理的应用程序可能会尝试捕获的情况。

1.1 出现异常的主要原因

  • 无效的用户输入
  • 设备故障
  • 失去网络连接
  • 物理限制(磁盘内存不足)
  • 代码错误
  • 打开一个不可用的文件

1.2 异常层次结构

所有异常和错误类型都是 Throwable 类的子类,Throwable是层次结构的基类。一类由Exception领导。此类用于用户程序应捕获的异常情况。NullPointerException 是此类异常的一个示例。

Java 运行时系统 ( JVM ) 使用另一个类别Error来指示与运行时环境本身 (JRE) 相关的错误。StackOverflowError 是此类错误的一个示例。

Java 中的异常层次结构

由于Error通常是程序员无法控制的,所以我们不应该试图去处理错误,我们只针对Exception这类处理即可。

1.3 异常类型

Java 定义了几种与其各种类库相关的异常类型。Java 还允许用户定义自己的异常。

异常类型

异常可以按两种方式分类:

  1. 内置异常
    • 检查异常
    • 未经检查的异常
  2. 用户定义的异常

让我们讨论上面定义的列出的异常,如下所示:

1.4 内置异常

内置异常是 Java 库中可用的异常。这些异常适用于解释某些错误情况。

  • 检查异常:已检查异常称为编译时异常,因为这些异常是在编译时由编译器检查的。
  • 未检查异常:未检查异常与检查异常正好相反。编译器不会在编译时检查这些异常。简单来说,如果一个程序抛出一个unchecked exception,即使我们没有处理或声明它,程序也不会给出编译错误。

未检查异常属于运行时异常,都继承RuntimeException。这些异常只有在运行的时候才能确定是否抛出异常。编译时不由编译器检查。

1.4.1 检查异常

这些 是在编译时检查的异常。如果方法中的某些代码抛出已检查的异常,则该方法必须处理该异常,或者必须使用throws关键字指定该异常。在检查异常中,有两种类型:完全检查异常和部分检查异常。一个完全检查的异常是一个检查异常,它的所有子类也被检查,像 IOException,InterruptedException。部分检查异常是一种检查异常,其中某些子类未检查,如 Exception。

例如,考虑下面的 Java 程序,它打开位于“C:.txt”位置的文件并打印它的前三行。该程序无法编译,因为函数 main() 使用 FileReader() 并且 FileReader() 抛出已检查的异常FileNotFoundException。它还使用 readLine() 和 close() 方法,这些方法也抛出已检查的异常IOException

例子:

1
2
3
4
5
6
7
8
public static void main(String[] args) {
FileReader file = new FileReader("C:\\test\\a.txt");
BufferedReader fileInput = new BufferedReader(file);
for (int counter = 0; counter < 3; counter++)
System.out.println(fileInput.readLine());
fileInput.close();
}

输出:

1
2
D:\workspace\project\IdeaProjects\demo\src\main\java\com\example\demo\aaa\DDD.java:58:27
java: 未报告的异常错误java.io.FileNotFoundException; 必须对其进行捕获或声明以便抛出

程序是无法运行的,因为我们必须手动处理这些检查异常,要修复上述程序,我们要么需要使用 throws 指定异常列表,要么需要使用 try-catch 块。即可使上述程序无编译错误。

1.4.2 非检查异常(运行时异常)

这些是编译时不检查的异常。在 C++ 中,所有异常都是未经检查的,因此编译器不会强制处理或指定异常。由程序员来文明,并指定或捕获异常。在 Java 中,ErrorRuntimeException类下的异常是非检查的异常,throwable 下的所有其他异常都是检查异常。

以下 Java 程序。它编译得很好,但在运行时会抛出ArithmeticException。编译器允许它编译,因为ArithmeticException是一个非检查的异常。

例子:

1
2
3
4
5
public static void main(String args[]){
int x = 0;
int y = 10;
int z = y / x;
}

输出:

1
2
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.example.demo.aaa.DDD.main(DDD.java:60)

简而言之,非检查的异常是不需要在 throws 子句中捕获或声明的运行时异常。这些异常通常是由编程错误引起的,例如试图越界访问数组中的索引或试图除以零。

非检查的异常包括 RuntimeException 类的所有子类,以及 Error 类及其子类。

1.5 用户自定义异常

异常是程序执行期间发生的问题(运行时错误)。当发生异常时,程序会突然终止,并且不会执行生成异常行之后的代码。

有时,Java 中内置的异常并不能描述某种情况。在这种情况下,用户还可以创建异常,称为“用户定义的异常”,并使用“throw”关键字抛出该异常。

为什么要使用自定义异常?

  • 捕获并提供对现有 Java 异常子集的特定处理。
  • 业务逻辑异常:这些是与业务逻辑和工作流相关的异常。这对于应用程序用户或开发人员了解确切的问题很有用。

为了创建自定义异常,我们需要扩展属于java.lang 包的 Exception 类。

例如,下面代码中的 MyException 扩展了 Exception 类。

自定义异常只需要继承RuntimeException即可。下列为一个简单的自定义异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyException extends RuntimeException{

private String errorCode;
private String errorMsg;

public MyException() {
super();
}

public MyException(String errorCode, String errorMsg) {
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
}

使用:

1
2
3
4
5
6
public static void main(String args[]){
... ...
//逻辑失败处理
throw new MyException("your errorCode","your errorMsg");

}

1.6 打印异常的方法

1.6.1 printStackTrace()

  • printStackTrace()– 该方法打印异常信息,格式为异常名称:异常描述,堆栈

例子:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
int a=5;
int b=0;
try{
System.out.println(a/b);
} catch(ArithmeticException e){
e.printStackTrace();
}
}

输出:

1
2
java.lang.ArithmeticException: / by zero
at com.example.demo.aaa.DDD.main(DDD.java:61)

1.6.2 toString()

  • toString() – 该方法以异常名称:异常描述的格式打印异常信息

例子:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
int a=5;
int b=0;
try{
System.out.println(a/b);
} catch(ArithmeticException e){
System.out.println(e.toString());
}
}

输出:

1
java.lang.ArithmeticException: / by zero

1.6.3 getMessage()

  • getMessage() - 该方法只打印异常的描述

例子:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
int a=5;
int b=0;
try{
System.out.println(a/b);
} catch(ArithmeticException e){
System.out.println(e.getMessage());
}
}

输出:

1
/ by zero

1.6.4 不处理异常

例子:

1
2
3
4
5
public static void main(String[] args) {
int a=5;
int b=0;
System.out.println(a/b);
}

则为:

1
2
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.example.demo.aaa.DDD.main(DDD.java:61)

2.JVM 是如何处理异常的?

默认异常处理:无论何时在方法内部,如果发生异常,该方法都会创建一个称为异常对象的对象,并将其交给运行时系统 (JVM)。

异常对象包含异常的名称和描述以及发生异常的程序的当前状态。创建异常对象并在运行时系统中处理它称为抛出异常。可能有一个已调用的方法列表,以到达发生异常的方法。这个有序的方法列表称为Call Stack。现在将发生以下过程。

  • 运行时系统搜索调用堆栈以找到包含可以处理发生的异常的代码块的方法。代码块称为Exception handler(异常处理程序)。
  • 运行时系统从发生异常的方法开始搜索,并按照调用方法的相反顺序遍历调用堆栈。
  • 如果找到合适的处理程序,则将发生的异常传递给它。合适的处理程序意味着抛出的异常对象的类型与它可以处理的异常对象的类型相匹配。
  • 如果运行时系统搜索调用堆栈上的所有方法,但找不到合适的处理程序,则运行时系统将 Exception Object 移交给默认的异常处理程序,它是运行时系统的一部分。该处理程序以下列格式打印异常信息并异常终止程序。
1
2
Exception in thread "xxx" Name of Exception : Description
... ...... .. // Call Stack

还记得上面,如果没有处理异常打印,它会默认打印:

1
2
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.example.demo.aaa.DDD.main(DDD.java:61)

调用堆栈的流程

调用堆栈

3.try-catch异常处理

针对于程序中异常的处理方式,一般有2种:

  • 手动抛出异常:throw new MyException("your errorCode", "your errorMsg")
  • 通过try-cathc-finally处理

例子:

1
2
3
4
5
public static void main(String[] args) {
int[] arr = new int[4];
int i = arr[4];
System.out.println("Hi, I want to execute");
}

输出:

1
2
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 4
at com.example.demo.aaa.DDD.main(DDD.java:59)

在上面的例子中,一个数组被定义为大小,即你只能访问索引 0 到 3 的元素。但是你试图访问索引 4 处的元素(错误地)这就是它抛出异常的原因。在这种情况下,JVM异常终止程序。语句System.out.println("Hi, I want to execute"); 永远不会执行。要执行它,我们必须使用 try-catch 处理异常。因此,为了继续程序的正常流程,我们需要一个 try-catch 子句。

1
2
3
4
5
6
7
8
9
10
11
try {
// block of code to monitor for errors
// the code you think can raise an exception
} catch (ExceptionType1 exOb) {
// exception handler for ExceptionType1
} catch (ExceptionType2 exOb) {
// exception handler for ExceptionType2
}
// optional
finally { // block of code to be executed after try block ends
}

以下要点:

  • 在一个方法中,可以有多个可能引发异常的语句,因此将所有这些语句放在它们自己的try块中,并在它们自己的catch块中为每个语句提供一个单独的异常处理程序。

  • 如果try块中发生异常,则该异常由与其关联的异常处理程序处理。要关联异常处理程序,我们必须在它之后放置一个catch块。可以有多个异常处理程序。每个catch块都是一个异常处理程序,用于处理由其参数指示的类型的异常。参数 ExceptionType 声明它可以处理的异常类型,并且必须是从Throwable类继承的类的名称。

  • 对于每个 try 块,可以有零个或多个 catch 块,但只有一个final 块。

    finally 块是可选的。无论 try 块中是否发生异常,它总是被执行。如果发生异常,那么它将在try 和 catch 块之后执行。 而如果没有发生异常,那么就会在try块之后执行。java中的finally块用于放置重要代码,例如清理代码,例如关闭文件或关闭连接。

4.常见的异常

  1. ArithmeticException: 当算术运算出现异常情况时抛出。
  2. ArrayIndexOutOfBoundsException:抛出它表示已使用非法索引访问数组。索引为负数或大于或等于数组的大小。
  3. ClassNotFoundException: 当我们尝试访问未找到其定义的类时引发此异常
  4. FileNotFoundException: 当文件不可访问或无法打开时会引发此异常。
  5. IOException: 当输入输出操作失败或中断时抛出
  6. InterruptedException: 当一个线程正在等待、休眠或做一些处理,被中断时抛出。
  7. NoSuchFieldException: 当类不包含指定的字段(或变量)时抛出
  8. NoSuchMethodException:访问未找到的方法时抛出。
  9. NullPointerException: 引用空对象的成员时引发此异常。Null 代表什么都没有
  10. NumberFormatException: 当方法无法将字符串转换为数字格式时会引发此异常。
  11. RuntimeException: 这表示在运行时发生的异常。
  12. StringIndexOutOfBoundsException: 它由 String 类方法抛出,指示索引为负数或大于字符串的大小
  13. IllegalArgumentException :当该方法接收到不完全符合给定关系或条件的参数时,此异常将抛出错误或错误语句。它属于未经检查的异常。
  14. IllegalStateException :当应用程序中的特定操作未访问该方法时,此异常将抛出错误或错误消息。它属于未经检查的异常。

博客说明

文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,不用于任何的商业用途。如有侵权,请联系本人删除。谢谢!


异常
https://nanchengjiumeng123.top/2023/01/30/java_se/2022-05-13_异常/
作者
Yang Xin
发布于
2023年1月30日
许可协议