异常
1.什么是异常
在Java中,异常(Exception)是指程序执行过程中可能出现的不正常情况或错误。它是一个事件,会干扰程序的正常执行流程,并可能导致程序出现错误或崩溃。异常在Java中是以对象的形式表示的,这些对象是从java.lang.Throwable
类或其子类派生而来。
定义:异常是在程序执行期间发生的事件,它中断正在执行的程序的正常指令流。为了能够及时有效地处理程序中的运行错误,Java专门引入了异常类。
2.Java异常类结构图
Java异常类结构是Java语言异常处理框架的基础,它允许程序在遇到预期或意外情况时,能够优雅地处理这些错误,而不是立即终止运行。Java异常类结构的核心是java.lang.Throwable
类,它是所有异常和错误的根类。
Throwable类及其子类
- Throwable:所有异常和错误的超类。它有两个直接子类:
Error
和Exception
。
Error类
- Error:表示程序无法恢复的严重错误,如系统崩溃、内存溢出等。这类错误通常不需要程序处理,因为它们通常是不可控的系统级错误。常见的Error子类包括
OutOfMemoryError
、StackOverflowError
等。
Exception类
- Exception:表示程序本身可以处理的异常。它进一步分为两大类:运行时异常(Unchecked Exception)和非运行时异常(Checked Exception,也称为编译时异常)。
3.Exception分类
3.1.运行时异常
-
运行时异常(RuntimeException及其子类):在程序运行时可能发生的异常,这些异常通常是由于程序中的逻辑错误导致的,而不是由外部因素(如文件不存在、网络问题等)引起的。Java编译器不要求程序必须显式地处理这些异常,但开发者仍然应该尽力避免它们的发生,以提高程序的健壮性和可靠性。
常见的运行时异常:
- NullPointerException(空指针异常):
- 当尝试访问或操作一个未初始化(即为null)的对象时抛出。
- 常见场景包括调用null对象的属性或方法。
- ArrayIndexOutOfBoundsException(数组索引越界异常):
- 当尝试访问数组的索引超出其有效范围时抛出。
- 例如,访问数组的负索引或超出数组长度的索引。
- ArithmeticException(算术异常):
- 在进行算术运算时,如果违反了运算规则(如整数除零),则抛出此异常。
- ClassCastException(类型转换异常):
- 当尝试将对象强制转换为不是其实例的子类时抛出。
- 例如,将String对象转换为Integer类型。
- IllegalArgumentException(非法参数异常):
- 当向方法传递了非法或不适当的参数时抛出。
- 例如,向方法传递了负数的数组长度。
- IllegalStateException(非法状态异常):
- 当对象的状态不允许进行某些操作时抛出。
- 例如,在已经关闭的流上调用读写方法。
- UnsupportedOperationException(不支持的操作异常):
- 当尝试调用对象上不支持的方法时抛出。
- 例如,在不可修改的集合上调用修改方法。
- ConcurrentModificationException(并发修改异常):
- 在多线程环境下,当某个线程在遍历集合的同时,另一个线程修改了该集合,导致遍历抛出此异常。
- NumberFormatException(数字格式异常):
- 当尝试将字符串转换为数字时,如果字符串的格式不正确,则抛出此异常。
- 例如,将非数字的字符串转换为整数。
- SecurityException(安全异常):
- 当安全管理器存在且其
checkPermission
方法不允许进行某些操作时抛出。
- 当安全管理器存在且其
- NullPointerException(空指针异常):
3.2.受检查异常
-
受检查异常(Checked Exception):受检查异常是指在编译时期就需要程序员进行处理的异常。这些异常通常是由外部因素引起的,如文件操作、数据库连接等,它们的发生是可以预见的,并且程序应该提供相应的处理逻辑来应对。这类异常在编译时必须显式处理,即要么在方法内部使用try-catch语句捕获并处理,要么在方法签名上通过throws关键字声明可能会抛出的异常。常见的非运行时异常包括
IOException
、SQLException
等。常见的受检查异常类:
- IOException:输入输出异常,表示在输入输出过程中发生的错误。这是最常见的受检查异常之一,几乎所有的文件读写、网络通信等操作都可能抛出这个异常。
- SQLException:SQL异常,表示数据库操作中发生的错误。例如,执行SQL语句时语法错误、连接数据库失败等。
- ClassNotFoundException:类未找到异常,当尝试加载一个类但找不到其定义时抛出。这通常发生在动态加载类时,如使用
Class.forName()
方法。 - FileNotFoundException:文件未找到异常,当试图打开一个不存在的文件时抛出。这个异常是
IOException
的子类,但它特别用于表示文件未找到的情况。 - EOFException:文件结束异常,当输入流到达文件末尾且没有更多数据可读时抛出。这也是
IOException
的一个子类。
4.异常处理机制
4.1.抛出异常(Throwing Exceptions)
在Java中,可以通过throw
关键字来显式地抛出一个异常。throw
后面跟的是一个异常对象,这个对象必须是Throwable
或其子类的实例。通常,我们会抛出Exception
或其子类的实例,以表示程序中的异常情况。
示例
public class TestThrow {
public static void main(String[] args) {
try {
throw new Exception("这是一个自定义的异常信息");
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
在这个示例中,main
方法内部通过throw
关键字抛出了一个Exception
对象,这个对象包含了字符串"这是一个自定义的异常信息"作为异常信息。然后,这个异常被try
块包围,并由紧随其后的catch
块捕获。在catch
块中,我们打印出了异常的信息。
4.2.捕获异常
在Java中,捕获异常是通过try-catch
语句来实现的。try
块用于包裹可能抛出异常的代码,而catch
块则用于捕获并处理这些异常。如果try
块中的代码抛出了异常,并且这个异常与某个catch
块中声明的异常类型相匹配,那么控制流就会跳转到该catch
块,并执行其中的代码。
基本的try-catch
结构
try {
// 尝试执行的代码,可能会抛出异常
} catch (ExceptionType1 e1) {
// 处理ExceptionType1类型的异常
} catch (ExceptionType2 e2) {
// 处理ExceptionType2类型的异常
// 可以有多个catch块来处理不同类型的异常
} finally {
// 可选的finally块,无论是否发生异常都会执行
// 通常用于释放资源,如关闭文件、数据库连接等
}
示例
public class TryCatchExample {
public static void main(String[] args) {
try {
int result = 10 / 0; // 这行代码会抛出ArithmeticException
} catch (ArithmeticException e) {
System.out.println("发生了算术异常: " + e.getMessage());
} finally {
System.out.println("finally块总是会被执行");
}
try {
String text = null;
int length = text.length(); // 这行代码会抛出NullPointerException
} catch (NullPointerException e) {
System.out.println("发生了空指针异常: " + e.getMessage());
}
// 注意:如果没有异常被抛出,catch块将不会被执行
try {
System.out.println("这条语句不会抛出异常");
} catch (Exception e) {
// 这里的代码不会被执行,因为没有异常被抛出
}
}
}
捕获多种类型的异常
你可以通过添加多个catch
块来捕获不同类型的异常。catch
块的顺序很重要,因为一旦某个catch
块匹配了抛出的异常,后续的catch
块就不会再被检查。因此,你应该先捕获最具体的异常,然后再捕获更一般的异常(如Exception
)。
捕获所有类型的异常
虽然不推荐这样做(因为它会隐藏错误),但你可以通过捕获Exception
类(所有异常类的超类)来捕获所有类型的受检查和非受检查异常。
try {
// 尝试执行的代码
} catch (Exception e) {
// 处理所有类型的异常
}
然而,请注意,RuntimeException
及其子类(非受检查异常)通常不需要显式捕获,因为Java编译器不要求你处理它们。但是,如果你确实想捕获并处理它们,你可以像上面那样做。
注意事项
finally
块是可选的,但如果你使用了它,无论是否发生异常,它都会被执行。这对于清理资源特别有用。- 如果在
try
块或catch
块中使用了return
、break
或continue
语句,并且存在finally
块,那么finally
块会在这些语句执行之前执行。但是,finally
块中的return
语句会覆盖try
或catch
块中的return
语句(如果有的话)。 - 尽量避免在
finally
块中抛出异常,因为这可能会掩盖原始异常。如果确实需要在finally
块中执行可能抛出异常的代码,请确保你能够妥善处理这些异常,或者至少将它们记录到日志中。
4.3.异常传播
在Java中,异常传播(Exception Propagation)是指当一个方法内部发生异常时,这个异常可以被抛出到调用该方法的代码中,进而可以继续向上传播,直到被捕获或程序终止。异常传播是Java异常处理机制的一个重要方面,它允许开发者在更高的层次上处理异常,从而避免在每个可能抛出异常的代码块中都进行异常处理。
异常传播的基本规则
- 抛出异常:当一个方法内部发生异常时,可以使用
throw
关键字抛出异常。如果这个方法没有捕获这个异常,那么它就会沿着调用栈向上传播。 - 捕获异常:在异常传播的路径上,如果某个方法使用
try-catch
语句捕获了异常,那么异常就不会继续向上传播。捕获异常后,可以在catch
块中处理异常,或者再次抛出异常(可以使用throw
关键字,或者通过throw new Exception(...)
抛出一个新的异常)。 - 声明抛出异常:如果一个方法可能抛出受检查异常(checked exception),但是该方法内部并没有捕获这个异常,那么这个方法就必须在方法签名上使用
throws
关键字声明这个异常。这样,调用这个方法的代码就必须处理这个异常,要么通过try-catch
语句捕获,要么继续向上抛出。 - 异常传播直到被捕获:异常会一直沿着调用栈向上传播,直到被某个
try-catch
语句捕获,或者直到到达程序的顶层(通常是main
方法或线程的入口点),此时如果没有被捕获,JVM会打印出异常的堆栈跟踪信息,并终止程序。
示例
public class ExceptionPropagationExample {
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
System.out.println("在main方法中捕获了异常: " + e.getMessage());
}
}
public static void method1() throws Exception {
method2();
}
public static void method2() throws Exception {
try {
// 假设这里有一些可能抛出异常的代码
throw new Exception("这是一个异常");
} catch (Exception e) {
// 在这里处理异常,或者重新抛出
// 这里我们选择重新抛出异常
throw e; // 异常继续向上传播
}
}
}
在这个示例中,method2
内部抛出了一个异常,由于method2
的catch
块中使用了throw e;
,这个异常被重新抛出。然后,这个异常沿着调用栈向上传播到method1
,但是method1
并没有捕获这个异常,而是使用throws
关键字声明了它。因此,这个异常继续向上传播到main
方法,并在那里被捕获和处理。
注意事项
- 合理地使用异常传播可以使代码更加清晰和易于维护,但是过度使用或滥用异常传播(如将异常用于控制程序流程)可能会导致代码难以理解和调试。
- 在设计API时,应该仔细考虑哪些异常是应该被声明为受检查异常(checked exception),哪些应该是非受检查异常(unchecked exception,即
RuntimeException
及其子类)。受检查异常要求调用者显式地处理异常,这有助于编写更健壮的代码;但是,如果过多地使用受检查异常,可能会使API的使用变得繁琐。
5.自定义异常
在Java中,自定义异常是通过继承Java的异常类(如Exception
或RuntimeException
及其子类)来实现的。自定义异常允许你定义具有特定含义的异常,这些异常可以更好地反映你的应用程序中可能发生的错误情况。
继承Exception
类
当你想要创建一个受检查的异常(checked exception)时,应该继承Exception
类。受检查的异常是那些在编译时要求被捕获或声明的异常。
public class MyCustomException extends Exception {
// 构造函数
public MyCustomException() {
super(); // 调用父类的无参构造函数
}
public MyCustomException(String message) {
super(message); // 调用父类的带有详细信息的构造函数
}
// 可以根据需要添加更多的构造函数和方法
}
继承RuntimeException
类
当你想要创建一个非受检查的异常(unchecked exception)时,应该继承RuntimeException
类或其子类。非受检查的异常不需要在方法签名中声明,也不需要被强制捕获。
public class MyRuntimeException extends RuntimeException {
// 构造函数
public MyRuntimeException() {
super(); // 调用父类的无参构造函数
}
public MyRuntimeException(String message) {
super(message); // 调用父类的带有详细信息的构造函数
}
// 可以根据需要添加更多的构造函数和方法
}
使用自定义异常
一旦你定义了自定义异常,就可以在代码中像使用Java标准异常那样使用它们了。例如,你可以在方法内部抛出自定义异常,或者在捕获到异常时抛出自定义异常来封装原始异常。
抛出自定义异常
public void doSomething() throws MyCustomException {
// 一些逻辑
if (/* 某种错误条件 */) {
throw new MyCustomException("发生了自定义异常");
}
}
捕获并处理自定义异常
try {
doSomething();
} catch (MyCustomException e) {
// 处理异常
System.out.println("捕获到自定义异常: " + e.getMessage());
}
标签:Exception,抛出,捕获,try,catch,异常
From: https://www.cnblogs.com/tubby233/p/18343278