Java异常的三种分类
- 受检异常(Checked Exceptions):最具代表性的是用户错误和问题引起的异常,程序员无法遇见,这些异常在编译时强制要求进行处理,比如打开一个不存在的文件。如果一个方法可能会引发受检异常,要么在方法签名中使用
throws
子句声明,要么使用try-catch块捕获并处理这些异常。例如,IOException
就是一个受检异常,它需要在代码中进行显式处理。 - 运行时异常(Runtime Exceptions):这些异常是
RuntimeException
类及其子类的实例。与受检异常不同,编译时会被忽略,运行时异常不需要显式地捕获或声明。常见的运行时异常包括NullPointerException
、ArrayIndexOutOfBoundsException
和ArithmeticException
等。 - 错误(Errors):错误通常指示了严重的问题,大多数情况下是程序无法处理的。与异常不同,错误通常不应该被捕获和处理。常见的错误包括
StackOverflowError
和OutOfMemoryError
等。
异常体系结构
Java的异常体系结构是基于Throwable类的,java.lang.Throwable
作为所有异常的超类
Error
Error
类是Java编程语言中表示严重问题的基类之一。
在Java中,Error
对象表示的是系统级的错误和运行时环境的问题,通常由Java虚拟机(JVM)生成和抛出。
与一般的异常不同,Error
及其子类通常表示虚拟机自身的问题,而不是应用程序的问题。
一些常见的Error
子类包括:
- OutOfMemoryError:当JVM无法再分配内存时抛出,表示内存耗尽。
- StackOverflowError:当方法调用的层级太深,导致栈空间耗尽时抛出。
- NoClassDefFoundError:当JVM或ClassLoader无法找到所需的类定义时抛出。
- InternalError:表示JVM内部发生了严重错误。
Exception
Exception
类是Java编程语言中表示一般意外情况或错误的基类之一。
在Java中,Exception
及其子类通常用于表示程序运行过程中可能发生的各种异常情况。
Exception
类的直接子类主要分为两种:
- Checked Exception:这些异常必须在代码中进行处理,要么通过try-catch块捕获和处理,要么通过throws子句声明向上传播。
- RuntimeException:这些异常通常是由程序错误引起的,不要求在代码中强制处理。
一些常见的 Checked Exception:
-
IOException:用于表示输入输出操作中可能发生的异常情况,如文件未找到、文件权限问题等。
-
SQLException:用于表示与数据库访问相关的异常,例如连接问题、查询问题等。
-
ClassNotFoundException:用于表示在运行时找不到特定类的异常。
-
FileNotFoundException:指示尝试打开指定文件名的文件失败。
-
ParseException:用于表示解析操作中可能发生的异常,例如日期解析错误。
-
NoSuchMethodException:指示尝试访问不存在的方法时发生的异常。
一些常见的 RuntimeException:
-
NullPointerException:当应用程序试图在要求使用对象的地方使用 null 时,抛出该异常。
-
ArrayIndexOutOfBoundsException:用于指示数组索引超出范围。
-
ArithmeticException:表示在算术运算中发生的异常情况,例如除以零。
-
IllegalArgumentException:指示向方法传递了一个不合法或不正确的参数。
-
IllegalStateException:表示对象的状态不适合请求的操作。
-
ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常。
异常处理机制
-
try-catch块:
try
用于包裹可能会引发异常的代码块,catch
用于捕获并处理特定类型的异常。如果try
块中的代码引发了异常,Java 将会跳到匹配的catch
块中执行相应的处理代码。javatry { // 可能引发异常的代码 } catch (ExceptionType e) { // 处理异常的代码 }
-
finally块:
finally
块中的代码总是会被执行,无论是否发生异常。通常用于确保资源的释放,如关闭文件或网络连接。javatry { // 可能引发异常的代码 } catch (ExceptionType e) { // 处理异常的代码 } finally { // 无论是否发生异常都会执行的代码 }
-
throws关键字:
throws
用于在方法声明中指定可能会抛出的异常,将异常的处理责任交给调用者。这就是告诉调用者,调用这个方法时,需要考虑到可能会抛出的异常情况。javavoid someMethod() throws SomeException { // 方法可能抛出 SomeException }
javavoid readFile() throws IOException { // 读取文件的代码 }
在这个例子中,
readFile
方法声明了可能会抛出IOException
异常。当其他代码调用readFile
方法时,调用者需要考虑到可能会出现IOException
异常的情况。 -
throw关键字:
throw
用于手动抛出一个异常。这意味着当程序执行到throw
语句时,会创建一个异常对象并将其抛出。这个异常可以是 Java 标准库中已经定义好的异常类型,也可以是你自己定义的异常类型。javaif (someCondition) { throw new SomeException("Error message"); }
举个例子,假设你正在编写一个函数,需要检查传入的参数是否满足某些条件,如果条件不满足,你可以使用
throw
抛出一个异常来中断程序的执行并给出相应的提示或错误信息。javavoid checkAge(int age) { if (age < 0) { throw new IllegalArgumentException("Age cannot be negative"); } }
在这个例子中,如果传入的年龄是负数,
throw
语句将会创建一个IllegalArgumentException
异常对象并将其抛出。 -
自定义异常:可以通过扩展
Exception
类或其子类来创建自定义异常类,以便应用程序可以根据特定的需求创建和使用异常类型。
如果不加
throws MyException
会报错,这是因为:在Java中,如果一个方法可能抛出已检查异常(checked exception),但没有使用try-catch
块捕获该异常,也没有使用throws
子句将该异常传播出去,编译器会报错。
几种异常处理方式的区别:
throw
用于手动抛出异常,throws
用于声明可能会抛出的异常类型,而try-catch
用于实际处理异常情况。throw
和try-catch
是用于实际处理异常的,而throws
是在方法声明中指明可能会抛出的异常类型。
举例:
java
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class FileHandlingExample {
public static void main(String[] args) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("example.txt"));
String line = reader.readLine();
int number = Integer.parseInt(line);
System.out.println("Read number: " + number);
} catch (FileNotFoundException e) {
System.err.println("File not found: " + e.getMessage());
} catch (NumberFormatException e) {
System.err.println("Invalid number format: " + e.getMessage());
} catch (IOException e) {
System.err.println("IOException: " + e.getMessage());
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
System.err.println("Error while closing the reader: " + e.getMessage());
}
}
}
}
在这个例子中,我们尝试打开一个文件并读取其中的内容。在 try
块中,我们打开文件并尝试读取文件中的一行内容,并将其转换为整数。在 catch
块中,我们分别捕获了 FileNotFoundException
(文件未找到)、NumberFormatException
(无效的数字格式)和 IOException
(输入输出异常)。
在 finally
块中,我们确保关闭了文件读取器,即使在 try
或 catch
块中发生了异常,也会执行这一步。这是非常重要的,因为它确保资源得到了正确的释放。
Java 编译器会按照 catch
块的顺序来匹配异常,而一旦找到了匹配的 catch
块,后续的 catch
块将不再执行。 因此,如果将最一般的异常类型放在最前面,那么可能会导致后续的 catch
块永远无法执行。
在前面提供的示例代码中,我们先捕获了 FileNotFoundException
,然后是 NumberFormatException
,最后是 IOException
。这是符合最佳实践的顺序。
如果按照相反的顺序,即先捕获 IOException
,然后是 NumberFormatException
,最后是 FileNotFoundException
,那么由于 IOException
是 FileNotFoundException
和 NumberFormatException
的父类,IOException
的 catch 块将会捕获到所有这些异常,而后续的 catch 块将无法执行。
因此,捕获异常时应该遵循从最具体的异常类型到最一般的异常类型的顺序,以确保程序能够按预期进行异常处理。
书写异常处理语句的快捷键:ctrl+alt(option)+T
手动结束程序语句:
实际使用中的经验总结:
- 处理运行时异常时,采用逻辑去合理规避同时辅助
try-catch
处理 - 在多重
catch
块后面,可以加上一个catch(Exception)
来处理可能会被遗漏的异常 - 对于不确定的代码,也可以加上
try-catch
处理异常 - 尽量去处理异常,切忌仅使用
printStackTrace()
打印输出 - 具体处理异常,根据业务需求和异常类型的不同决定
- 尽量添加
finally
释放占用的资源(IO、Scanner)