当前位置: 首页 > news >正文

[Java]异常

在程序运行时,如果遇到问题(比如除以零、文件找不到等),程序会发生异常。异常就像是程序的“错误提醒”,当程序运行中出错时,它会停止,给出一个错误信息。我们可以通过异常处理来控制这些错误,避免程序崩溃。异常处理机制基于“try-catch-finally”语法。

1.异常的分类

  • 编译时异常(Checked Exception):这种异常是编译器检查到的,程序必须处理这种异常。比如,文件找不到时,Java会要求你去处理这个问题。必须捕获或声明处理。

    • 例子:IOException(文件操作时出现错误)。
  • 运行时异常(Unchecked Exception):这种异常发生在程序运行时,通常是一些小错误,程序员可以选择是否处理它。比如,除以零就是一个运行时异常。RuntimeException是 Java 中常见的运行时异常,它是所有 运行时异常(Unchecked Exceptions)类的父类。RuntimeException 及其子类的异常通常是在程序运行时抛出的,而不像 Checked Exception(如 IOExceptionSQLException 等)那样需要显式地在方法签名中声明或捕获。不需要显式捕获,但可以捕获。下面第五点有关于RuntimeException的详细介绍。

    • 例子:ArithmeticException(除以零),NullPointerException(空指针错误)。

2.异常处理

Java提供了try-catch-finally语法来处理异常。

2.1 try-catch:捕获异常并处理

  • try块中包含可能会抛出异常的代码。
  • catch块用于捕获特定类型的异常。
    try {int result = 10 / 0; // 可能会抛出 ArithmeticException
    } catch (ArithmeticException e) {System.out.println("除以零错误:" + e.getMessage());
    }
    

2.2 finally

无论是否发生异常,finally中的代码总是会执行。通常用于清理资源,比如关闭文件流、数据库连接等。

try {// 可能发生异常的代码
} catch (Exception e) {// 异常处理代码
} finally {// 清理资源的代码
}

示例:

import java.io.FileReader;
import java.io.IOException;public class FinallyExample {public static void main(String[] args) {FileReader file = null;try {// 尝试打开文件并读取file = new FileReader("test.txt");  // 假设文件存在int data = file.read();System.out.println((char) data);} catch (IOException e) {System.out.println("文件读取失败:" + e.getMessage());} finally {// 确保文件流总是关闭,即使发生了异常try {if (file != null) {file.close();System.out.println("文件已关闭。");}} catch (IOException e) {System.out.println("关闭文件时出错:" + e.getMessage());}}}
}
/*
输出:
文件已关闭。
*/

 解释:

  • try 块中我们尝试打开一个文件并读取其中的内容。
  • 如果文件读取过程中没有出现异常,finally 块会确保文件流被关闭。
  • finally 块中的代码无论是否发生异常都会执行,这对于资源管理(如关闭文件流、数据库连接等)非常重要。

3.抛出异常

可以通过throw关键字手动抛出一个异常。通过throws声明方法可能抛出的异常。

3.1 throw:用来抛出一个异常对象。

throw new ArithmeticException("除以零异常");

3.2 throws:用来声明方法可能抛出的异常。

public void myMethod() throws IOException {// 可能抛出IOException的方法
}

综合使用示例: 

public class ThrowFinallyExample {public static void main(String[] args) {try {processFile("test.txt");} catch (IOException e) {System.out.println("捕获到异常: " + e.getMessage());}}// 处理文件的方法,可能会抛出 IOExceptionpublic static void processFile(String fileName) throws IOException {try {System.out.println("开始处理文件:" + fileName);// 假设处理过程中发生了异常if (fileName == null) {throw new IOException("文件名不能为空!");}System.out.println("文件处理完成。");} catch (IOException e) {System.out.println("处理文件时发生异常:" + e.getMessage());throw e;  // 将异常抛到外层} finally {// 这里模拟资源关闭,如果发生了错误,就抛出异常System.out.println("资源清理中...");if (fileName == null) {throw new IOException("资源清理失败:文件名为空!");}System.out.println("资源清理完成。");}}
}
/*
输出:
开始处理文件:test.txt
资源清理中...
资源清理完成。
*/

解释:

  • processFile 方法中,首先模拟文件处理过程中发生的 IOException
  • catch 捕获并处理该异常后,将其重新抛出,传递到方法外层。
  • finally 块中,无论是否发生异常,都执行资源清理的代码。如果清理过程中发生了问题,我们会抛出一个新的异常。

3.3 总结:

  1. throw:用于显式抛出异常。你可以在代码中主动抛出异常,以便在某些条件不满足时提前中止执行,提示错误。
  2. finally:用于保证无论是否发生异常,某些代码都会执行,通常用于清理工作,如关闭文件流、数据库连接等资源。
  • throw 抛出的异常需要在 try-catch 中进行捕获,或者通过 throws 声明抛出。
  • finally 块中的代码始终会执行,即使 try 块或 catch 块抛出异常。

4.常见的异常类

4.1 Throwable:是Java异常体系的根类,所有异常类的父类。

4.1.1 Error

Error 主要指系统级错误,通常不应该尝试捕获这些错误。因为一旦出现了 Error 类型的异常,程序通常会无法恢复,例如 OutOfMemoryError 或StackOverflowError。在实际开发中,遇到这类错误时通常是代码本身或者运行环境出现了问题,需要从根本上修复,而不是捕获异常后继续执行(即一般不需要捕获,而需要你手动把代码改正确来)。

4.1.2 Exception

Exception 是程序中的常见错误,我们可以通过 try-catch 语句进行捕获和处理。常见的异常包括 IOExceptionArithmeticExceptionNullPointerException 等。我们需要根据具体异常选择合适的处理方式。

4.2 常见的异常类

4.2.1 IOException

输入输出操作异常,例如文件读取时文件不存在或者无法读取就会抛出IOException错误。

import java.io.*;public class IOExceptionExample {public static void main(String[] args) {try {// 打开一个不存在的文件,会抛出 IOExceptionFileReader file = new FileReader("nonexistentfile.txt");BufferedReader reader = new BufferedReader(file);reader.read();reader.close();} catch (IOException e) {// 捕获并处理 IOExceptionSystem.out.println("文件操作异常:" + e.getMessage());}}
}

解释:代码尝试打开一个不存在的文件,导致抛出 IOException。在 catch 块中,我们捕获该异常并打印错误信息,而不是让程序崩溃。


4.2.2 NullPointerException

当你试图访问或操作一个 null 对象时,JVM 无法执行相关操作,因此抛出 NullPointerException。这种异常通常会发生在以下几种情况中:

  • 试图调用 null 引用的实例方法。
  • 试图访问 null 引用的字段。
  • 试图获取 null 引用的数组长度。
  • 试图将 null 引用传递给需要非 null 参数的方法。
public class NullPointerExceptionExample {public static void main(String[] args) {try {String str = null;System.out.println(str.length());  // str 为 null,会抛出 NullPointerException} catch (NullPointerException e) {// 捕获并处理空指针异常System.out.println("发生了空指针异常: " + e.getMessage());}}
}

解释:strnull,调用 str.length() 会抛出 NullPointerException。我们通过 try-catch 捕获该异常,避免程序崩溃。


4.2.3 ArithmeticException

算术运算异常,例如当你进行除法操作时,如果除数为零,程序会抛出 ArithmeticException

public class ArithmeticExceptionExample {public static void main(String[] args) {try {int result = 10 / 0;  // 除以零,抛出 ArithmeticException} catch (ArithmeticException e) {// 捕获并处理除零错误System.out.println("发生了算术异常: " + e.getMessage());}}
}

解释:除以零会抛出 ArithmeticException,我们捕获异常并输出错误信息,而不是让程序崩溃。


4.2.4 ArrayIndexOutOfBoundsException

当你尝试访问一个数组时,使用了无效的索引(即越界索引)时,会抛出ArrayIndexOutOfBoundsException异常。

public class ArrayIndexOutOfBoundsExceptionExample {public static void main(String[] args) {int[] arr = new int[3];  // 创建一个长度为 3 的数组try {// 尝试访问不存在的索引,数组长度为 3,最大索引为 2arr[5] = 10;  // 这会抛出 ArrayIndexOutOfBoundsException} catch (ArrayIndexOutOfBoundsException e) {System.out.println("发生了数组下标越界异常: " + e.getMessage());}}
}

解释:数组 arr 的长度为 3,它的有效索引是 0, 1, 2。但是我们尝试访问索引 5,这是越界的,导致 ArrayIndexOutOfBoundsException 异常。通过 try-catch 语句捕获异常,我们避免了程序崩溃,并输出了错误信息。


4.2.5 ClassNotFoundException

ClassNotFoundExceptionException 类的子类,通常发生在 动态加载类 时,如果找不到指定的类,会抛出此异常。常见于使用反射、Class.forName() 或类加载器时。

发生的原因:
  • 你试图通过 Class.forName()ClassLoader.loadClass() 等方法动态加载一个类,但该类在 classpath 中无法找到。
  • 这通常发生在类路径配置错误,或者尝试加载一个未编译或缺失的类时。
    public class ClassNotFoundExceptionExample {public static void main(String[] args) {try {// 使用 Class.forName 加载不存在的类Class.forName("com.example.NonExistentClass");} catch (ClassNotFoundException e) {System.out.println("发生了类未找到异常: " + e.getMessage());}}
    }
    

    解释:这里我们使用 Class.forName("com.example.NonExistentClass") 来加载一个不存在的类,这会抛出 ClassNotFoundException 异常。通过 try-catch 捕获异常并输出错误信息,避免了程序崩溃。

解决方法:

  • 确保类路径配置正确,类文件已经编译并位于 classpath 下。
  • 在动态加载类之前,可以使用 ClassLoadergetResource()getResourceAsStream() 等方法检查类是否存在。
    ClassLoader classLoader = getClass().getClassLoader();
    if (classLoader.getResource("com/example/NonExistentClass.class") != null) {// 类存在,可以加载Class.forName("com.example.NonExistentClass");
    } else {System.out.println("类文件不存在!");
    }
    
    反射中使用 Class.forName()

    通常情况下,我们会用反射动态加载类,尤其是在类名只有在运行时才能确定时。

    public class ReflectionExample {public static void main(String[] args) {try {// 动态加载一个类Class<?> clazz = Class.forName("java.util.ArrayList");System.out.println("加载成功: " + clazz.getName());} catch (ClassNotFoundException e) {System.out.println("类未找到: " + e.getMessage());}}
    }
    

    解释:

  • 这段代码成功加载了 java.util.ArrayList 类并打印出类的名称。
  • 如果 Class.forName() 中提供的类名无法找到,程序会抛出 ClassNotFoundException

4.2.6 FileNotFoundException(文件未找到异常):

  • FileNotFoundException 是一种输入输出异常,通常发生在你尝试访问一个不存在的文件时。
  • 比如,尝试打开一个根本没有的文件,就会抛出这个异常。
    File file = new File("nonexistentfile.txt");
    FileReader fr = new FileReader(file);  // 如果文件不存在,会抛出 FileNotFoundException
    

    如何避免?

  • 在访问文件时,先检查文件是否存在:

    File file = new File("nonexistentfile.txt");
    if (file.exists()) {FileReader fr = new FileReader(file);
    } else {System.out.println("文件不存在!");
    }
    

    4.2.7 SQLException(SQL 异常):

  • SQLException 是数据库操作中常见的异常,通常发生在数据库查询失败时。
  • 比如,执行错误的 SQL 查询语句时,会抛出这个异常。
    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/test", "user", "password");
    Statement stmt = conn.createStatement();
    stmt.executeUpdate("INVALID SQL QUERY");  // 会抛出 SQLException
    

    如何避免?

  • 在执行 SQL 操作时,确保 SQL 语句的正确性,并正确处理异常。
    try {stmt.executeUpdate("SELECT * FROM users");  // 正确的 SQL 查询
    } catch (SQLException e) {System.out.println("SQL 执行错误:" + e.getMessage());
    }
    

5.自定义异常

5.1 解释:

可以根据需要定义自己的异常类,通常自定义异常类需要继承ExceptionRuntimeException

class MyException extends Exception {public MyException(String message) {super(message);}
}public class Test {public static void test() throws MyException {throw new MyException("自定义异常");}public static void main(String[] args) {try {test();} catch (MyException e) {System.out.println("捕获到自定义异常:" + e.getMessage());}}
}

5.2 关于RuntimeException:

5.2.1 RuntimeException 的基本介绍

  • 继承关系RuntimeException 继承自 Exception 类,并且是 未检查异常(Unchecked Exception)的基类。
  • 运行时异常:运行时异常通常表示程序的逻辑错误或不合适的状态,开发者不需要强制捕获这些异常。
  • 不需要显式声明:与受检查异常不同,RuntimeException 不要求你在方法中使用 throws 声明它,且也不强制在代码中进行 try-catch 捕获。

5.2.2 常见的 RuntimeException 子类

  • NullPointerException:访问空对象引用时抛出的异常。
  • ArithmeticException:发生算术运算错误时抛出的异常,例如除以零。
  • ArrayIndexOutOfBoundsException:访问数组时,索引越界时抛出的异常。
  • ClassCastException:进行不合法的类型转换时抛出的异常。
  • IllegalArgumentException:当方法接收到不合法的参数时抛出的异常。
  • IllegalStateException:方法被调用时,当前对象状态不合法时抛出的异常。

5.2.3 RuntimeException 的特点

  • 不需要捕获RuntimeException 是未检查异常,所以开发者不必在代码中显式捕获它,也不必在方法签名中声明它。
  • 通常是程序错误:运行时异常通常是因为代码中的逻辑错误或者数据错误,比如访问空指针、数组越界等。修复这些异常一般需要修改代码逻辑。

5.2.4 如何使用 RuntimeException

虽然大多数情况下,RuntimeException 及其子类是由 JVM 自动抛出的,但你也可以在自己的代码中显式抛出 RuntimeException 或其子类,来指示某种错误。

抛出 RuntimeException 示例:

public class RuntimeExceptionExample {public static void main(String[] args) {try {checkAge(15);  // 传入一个非法的年龄值,抛出异常} catch (RuntimeException e) {System.out.println("捕获到异常: " + e.getMessage());}}// 自定义方法,检查年龄是否合法public static void checkAge(int age) {if (age < 18) {throw new IllegalArgumentException("年龄不能小于 18!");}System.out.println("年龄合格:" + age);}
}
/*
捕获到异常: 年龄不能小于 18!
*/

解释:

  • checkAge 方法中,如果传入的年龄小于 18,则主动抛出一个 IllegalArgumentException 异常,表示年龄不合法。
  • RuntimeException 的子类 IllegalArgumentException 被抛出,并在 catch 块中捕获和处理。

5.2.5 为什么使用 RuntimeException

RuntimeException 和其他未检查异常(Unchecked Exception)通常用于:

  • 表示代码逻辑中的错误,而不是外部条件导致的异常。
  • 表示不容易预见或者不容易恢复的错误,开发者可以通过修改代码来避免这种错误发生。
  • 用于捕捉错误输入、错误参数、程序不符合逻辑的状态等情况。

5.2.6 RuntimeException 与其他异常的区别

特性RuntimeExceptionIOExceptionSQLException
检查异常/非检查异常非检查异常(Unchecked Exception)检查异常(Checked Exception)
是否强制捕获不强制捕获或声明强制捕获或声明
抛出原因程序逻辑错误、非法操作外部因素、资源不可用等
常见场景空指针、除零、数组越界等文件读写失败、数据库操作失败等

5.2.7 何时使用 RuntimeException

你可以在以下情况使用 RuntimeException

  • 不合法的参数:当方法的参数不符合预期时(如负数、空值等),你可以抛出一个 IllegalArgumentException
  • 非法的状态:当对象的状态不适合调用某个方法时,可以抛出一个 IllegalStateException
  • 算术错误:如除以零时,抛出 ArithmeticException
  • 不合理的类型转换:如进行不合法的类型强制转换,抛出 ClassCastException

6.异常链

异常链(Exception Chaining)是指在捕获异常时,将原始异常作为另一个异常的原因(cause)抛出。Java 提供了一种机制,允许我们在抛出新的异常时,把原本抛出的异常附加到新的异常中。这样做可以帮助我们保留原始异常的详细信息,方便后续调试和问题定位。

6.1 异常链的作用

异常链的主要作用是帮助我们追踪问题的根源。当我们捕获到异常后,可以将其作为另一个异常的原因抛出,这样就能保留原始异常的信息,便于定位问题的源头。例如,捕获一个 SQLException,并将其作为 IOException 的原因重新抛出,方便上层调用者了解到底是哪里出的问题。

6.2 异常链的基本使用

Java 提供了 Throwable 类的构造方法,允许我们在抛出异常时指定一个原始的异常对象:

public Throwable(String message, Throwable cause)

public Throwable(String message, Throwable cause) 是 Java 中 Throwable 类的一个构造方法,它用于创建一个带有 错误消息原始异常 的异常对象。我们通常使用它来创建新的异常,同时保存引起该异常的原始原因。

6.2.1 构造方法的参数说明

  • String message:这个参数是一个 错误消息,用于描述当前异常的具体情况。通常是一个简短的字符串,用来说明异常的原因或上下文。例如:"File not found"
  • Throwable cause:这个参数是另一个异常对象,表示 导致当前异常的原始异常。它通常是一个已经存在的异常对象,我们把它传递到新的异常中来形成 异常链

6.2.2 构造函数的作用

  • 当我们在程序中捕获到一个异常,并且想要抛出一个新的异常时,可以通过这个构造函数将 原始异常cause)传递给新异常。
  • 这样做的目的是保留 原始异常 的信息,帮助开发者追踪错误的根源。它是一种 异常链 技术,可以让我们知道一个异常是如何引发其他异常的。

6.2.3 为什么使用这个构造函数?

使用这个构造函数的好处是:

  • 我们可以 抛出新的异常,并且 保留原始异常的上下文信息,从而有助于追踪问题的根源。
  • 通过 getCause() 方法,后续的异常处理者能够获取到原始异常并进行处理。

6.2.4 例子:如何使用 Throwable(String message, Throwable cause)

假设我们在处理一个文件操作时发生了异常,我们可以通过异常链来传递原始的 IOException 异常,使得上层调用者能够获取到详细的错误信息。

代码示例:
public class ExceptionChainingDemo {public static void main(String[] args) {try {processFile();} catch (Exception e) {e.printStackTrace();}}public static void processFile() throws Exception {try {openFile();} catch (Exception e) {// 将原始异常 e 包装到一个新的异常中并抛出throw new Exception("Failed to process the file", e);}}public static void openFile() throws Exception {// 模拟抛出文件操作异常throw new java.io.IOException("File not found");}
}
输出:
java.lang.Exception: Failed to process the fileat ExceptionChainingDemo.processFile(ExceptionChainingDemo.java:9)at ExceptionChainingDemo.main(ExceptionChainingDemo.java:4)
Caused by: java.io.IOException: File not foundat ExceptionChainingDemo.openFile(ExceptionChainingDemo.java:15)at ExceptionChainingDemo.processFile(ExceptionChainingDemo.java:7)... 1 more

6.2.5 解释

  • 在上面的代码中:
    • openFile() 方法抛出了一个 IOException 异常(模拟文件未找到的情况)。
    • processFile() 方法中,我们捕获了这个 IOException 异常,并且通过 new Exception("Failed to process the file", e) 创建了一个新的 Exception 异常。
    • 这里的 "Failed to process the file" 是新的异常的描述信息,而 e(即原始的 IOException 异常)被传递作为 原始异常(即 cause)。
  • 当我们打印异常信息时,printStackTrace() 显示了当前异常的信息,并且通过 Caused by 显示了引发当前异常的原始异常。

 

6.2.6 使用 ExceptionRuntimeException 创建异常链

Exception 类和 RuntimeException 类都提供了带有 cause 参数的构造函数,因此你可以在抛出这两种异常时都使用异常链。

示例:创建 RuntimeException 异常链
public class RuntimeExceptionChaining {public static void main(String[] args) {try {method1();} catch (RuntimeException e) {// 捕获并输出异常信息,显示异常链System.out.println("Caught exception: " + e);Throwable cause = e.getCause();if (cause != null) {System.out.println("Cause: " + cause);}}}public static void method1() {try {method2();} catch (RuntimeException e) {// 捕获 method2 中的异常,并将其作为 method1 的原因throw new RuntimeException("Error occurred in method1", e);}}public static void method2() {// 模拟抛出一个 RuntimeExceptionthrow new RuntimeException("An error occurred in method2");}
}

输出:

Caught exception: java.lang.RuntimeException: Error occurred in method1
Cause: java.lang.RuntimeException: An error occurred in method2

6.2.7 为什么要使用异常链?

  1. 保留原始异常信息:当我们在捕获到一个异常后,可以将它附加到一个新的异常中,这样上层代码就可以通过 getCause() 方法查看到原始的异常信息,帮助定位问题。

  2. 帮助追踪错误的根源:异常链可以让我们清楚地看到异常是如何传播的,特别是在复杂的系统中,错误可能从底层层级一直传递到上层应用,使用异常链可以更容易追踪整个错误过程。

  3. 提高代码可读性:通过异常链,能够避免在捕获异常后丢失原始的错误信息,增强代码的可读性和可维护性。

6.2.8 getCause()printStackTrace()

  • getCause():可以用来获取原始异常(如果存在的话)。
  • printStackTrace():会打印当前异常以及异常链中的所有异常。
6.2.8.1 getCause() 方法

getCause() 方法用于获取当前异常的原始异常(即引发当前异常的异常)。它是 Throwable 类的一部分,所有异常类(包括 ExceptionError)都继承自 Throwable,因此都可以使用该方法。

1. 功能
  • getCause() 返回的是一个 Throwable 对象,它代表的是导致当前异常发生的原始异常(如果存在的话)。
  • 如果当前异常是直接抛出的,没有原始异常,则返回 null
2. 示例:
public class GetCauseExample {public static void main(String[] args) {try {method1();} catch (Exception e) {// 获取并打印原始异常System.out.println("Caught exception: " + e.getMessage());if (e.getCause() != null) {System.out.println("Cause: " + e.getCause());}}}public static void method1() throws Exception {try {method2();} catch (Exception e) {// 捕获异常并将其作为原因抛出throw new Exception("Error in method1", e);}}public static void method2() throws Exception {// 模拟抛出一个异常throw new Exception("Error in method2");}
}

输出:

Caught exception: Error in method1
Cause: java.lang.Exception: Error in method2
3. 解释
  • method2() 抛出了一个异常:"Error in method2"。
  • method1() 捕获了该异常,并将它作为原因(cause)抛出了一个新的异常:"Error in method1"。
  • main() 方法中,我们捕获了这个新的异常,并通过 getCause() 方法获取到原始的异常信息。
6.2.8.2 printStackTrace() 方法

printStackTrace()Throwable 类的一个方法,它用于打印异常的堆栈跟踪信息。堆栈跟踪信息通常包含以下内容:

  • 异常的类型和消息。
  • 异常发生时的方法调用栈(即方法的调用路径)。
  • 异常发生的具体位置(行号和类名)。
1. 功能
  • printStackTrace() 方法会将异常的堆栈信息输出到控制台,帮助开发人员了解异常发生的详细上下文。
2. 示例:
public class PrintStackTraceExample {public static void main(String[] args) {try {method1();} catch (Exception e) {// 打印异常的堆栈信息e.printStackTrace();}}public static void method1() throws Exception {try {method2();} catch (Exception e) {// 捕获异常并将其作为原因抛出throw new Exception("Error in method1", e);}}public static void method2() throws Exception {// 模拟抛出一个异常throw new Exception("Error in method2");}
}

输出:

java.lang.Exception: Error in method1at PrintStackTraceExample.method1(PrintStackTraceExample.java:10)at PrintStackTraceExample.main(PrintStackTraceExample.java:5)
Caused by: java.lang.Exception: Error in method2at PrintStackTraceExample.method2(PrintStackTraceExample.java:16)at PrintStackTraceExample.method1(PrintStackTraceExample.java:8)... 1 more
3. 解释
  • method2() 中,我们抛出了一个异常 "Error in method2",然后在 method1() 中捕获该异常并将其作为原因抛出了新的异常 "Error in method1"
  • 当在 main() 中捕获到异常并调用 printStackTrace() 时,异常信息不仅显示当前异常,还显示了由原始异常引起的 Caused by 部分。这样,我们可以清晰地看到异常链,追踪错误的根本原因。

7.异常的最佳实践

  1. 捕获特定异常:尽量捕获具体的异常,而不是捕获Exception
  2. 不要忽略异常:避免捕获异常后什么都不做,这会隐藏程序中的问题。
  3. 及时释放资源:在finally中关闭文件流、数据库连接等资源,确保资源能够正确释放。
  4. 使用自定义异常:在合适的情况下,定义并抛出自定义异常,提供更加具体的错误信息。

8.异常的传递

在Java中,异常可以在方法内部被捕获并处理,也可以向上传递。异常的传递是通过方法声明中的throws来实现的。如果方法中抛出了异常且该异常没有被处理,Java虚拟机会将其传递给调用该方法的地方。

8.1 异常传播

8.1.1 解释:

如果一个方法抛出一个异常,而该方法的调用者没有处理(即没有捕获或声明throws),这个异常将会被继续抛出,直到它被某个方法捕获或最终未被捕获而导致程序终止。

public void methodA() throws Exception {methodB(); // methodB 可能抛出异常
}public void methodB() throws Exception {throw new Exception("Something went wrong");
}

 异常的多层次处理: 在多层方法调用中,如果外层方法没有处理异常,内层方法抛出的异常就会一直向上传递。

try {methodA(); // methodA 中会抛出异常
} catch (Exception e) {System.out.println("异常被捕获:" + e.getMessage());
}

8.1.2 具体代码举例:

public class ExceptionHandlingExample {// methodA 抛出 Exceptionpublic void methodA() throws Exception {System.out.println("In methodA");methodB();  // 调用 methodB,methodB 可能抛出异常}// methodB 抛出一个异常public void methodB() throws Exception {System.out.println("In methodB");// 模拟抛出异常throw new Exception("Something went wrong in methodB");}public static void main(String[] args) {ExceptionHandlingExample example = new ExceptionHandlingExample();try {example.methodA();  // 调用 methodA,methodA 中会调用 methodB,methodB 抛出异常} catch (Exception e) {// 捕获异常并处理System.out.println("异常被捕获: " + e.getMessage());  // 打印异常消息e.printStackTrace();  // 打印异常的堆栈信息}}
}

8.1.3 代码说明

methodA

  • methodA 声明 throws Exception,意味着它会抛出 Exception 类型的异常。
  • methodA 中,我们调用了 methodB(),而 methodB 可能会抛出一个异常。

methodB

methodB 也声明了 throws Exception,表示该方法可能会抛出异常。

methodB 中,我们模拟抛出了一个 Exception,并传递了错误消息 "Something went wrong in methodB"

main 方法

  • main 方法中,我们创建了 ExceptionHandlingExample 的实例,并调用 methodA()
  • methodA() 调用 methodB(),而 methodB() 会抛出一个异常,因此 methodA() 也会抛出异常。
  • try 块中,我们捕获了 methodA() 抛出的异常,使用 catch 语句块处理异常。
  • 我们使用 e.getMessage() 打印异常的消息,并使用 e.printStackTrace() 打印异常的堆栈跟踪信息。

输出结果

In methodA
In methodB
异常被捕获: Something went wrong in methodB
java.lang.Exception: Something went wrong in methodBat ExceptionHandlingExample.methodB(ExceptionHandlingExample.java:17)at ExceptionHandlingExample.methodA(ExceptionHandlingExample.java:9)at ExceptionHandlingExample.main(ExceptionHandlingExample.java:27)

解释

  1. 程序首先进入 methodA,然后调用 methodB
  2. methodB 抛出了一个异常 "Something went wrong in methodB",并且异常被 methodA 捕获。
  3. methodA 继续抛出该异常,最终在 main 方法中的 try-catch 块中捕获到这个异常。
  4. 异常的消息 "Something went wrong in methodB" 被打印出来,且 e.printStackTrace() 打印了详细的堆栈信息,显示异常是如何从 methodB 传递到 methodA 的。

9.捕获多个异常

9.1 解释:

Java 7 引入了多重异常捕获(Multi-catch),允许在一个 catch 块中捕获多个异常,这样可以减少重复的代码,并提高代码的可读性。你只需要在 catch 块中的异常类型之间使用 |(管道符)进行分隔。

9.2 代码格式

try {// 可能发生异常的代码
} catch (IOException | SQLException e) {  // 捕获多种异常System.out.println("发生异常:" + e.getMessage());
}

9.3 要求

  • 多个异常类必须有共同的父类,通常是 Exception 或其子类(例如:IOExceptionSQLException 都是 Exception 的子类)。
  • catch 块中,捕获的异常类的对象(这里是 e)会变成 Throwable 的父类,因此你不能再对 e 进行多态特有的方法调用。

9.4 具体例子

假设我们有两个异常:IOException(输入输出异常)和 SQLException(SQL 异常)。我们会模拟一个程序,在操作文件和数据库时分别抛出这两个异常,并使用 Java 7 的多重异常捕获来处理。

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.SQLException;public class MultiCatchExample {// 模拟读取文件public static void readFile() throws IOException {// 模拟文件未找到异常throw new FileNotFoundException("文件未找到");}// 模拟数据库操作public static void connectToDatabase() throws SQLException {// 模拟SQL异常throw new SQLException("数据库连接失败");}public static void main(String[] args) {try {readFile();  // 可能抛出 IOExceptionconnectToDatabase();  // 可能抛出 SQLException} catch (IOException | SQLException e) {  // 捕获多种异常System.out.println("发生异常:" + e.getMessage());}}
}

9.4.1 代码解释

  1. readFile() 方法

    • 该方法模拟读取文件的操作,可能会抛出 IOException 类型的异常。为了演示,我们使用 FileNotFoundException(它是 IOException 的子类)来模拟文件未找到的异常。
  2. connectToDatabase() 方法

    • 该方法模拟连接数据库的操作,可能会抛出 SQLException 类型的异常。我们直接抛出一个 SQLException
  3. main() 方法

    • 我们在 try 块中依次调用 readFile()connectToDatabase() 方法,这两个方法都有可能抛出异常。
    • catch 块中,我们使用 | 操作符捕获了 IOExceptionSQLException,并通过 e.getMessage() 打印了异常消息。

9.4.2 输出结果

发生异常:文件未找到

9.5 总结:

  • 多重异常捕获:Java 7 引入了多重异常捕获,允许你在同一个 catch 块中捕获多个异常。你只需使用 | 分隔异常类,如 IOException | SQLException
  • 减少重复代码:这种方式让你避免了为每个异常写一个 catch 块的冗余代码,从而使代码更简洁、可读性更强。
  • 共同父类:多个异常类必须有共同的父类,通常是 Exception 或其子类,否则不能进行多重异常捕获。

10.异常的性能

异常处理会影响程序的性能,尤其是在频繁抛出异常的情况下。为了优化性能,应该避免在正常的程序流程中使用异常。例如,不应该使用异常来控制程序流程,尤其是在循环或频繁执行的代码块中。

  1. 异常的成本: 抛出异常是一个相对昂贵的操作,因为它需要创建异常对象并进行堆栈跟踪。因此,最好在必要时才抛出异常。

  2. 异常的优化

    • 避免过多的try-catch块,尤其是在循环中。
    • 捕获异常的块应尽量简短,不要做复杂的逻辑处理。

11.异常的嵌套与多线程中的异常处理

  1. 嵌套异常: 异常可能会嵌套。例如,一个方法抛出的异常被另一个方法捕获并进一步抛出,这样形成了嵌套异常链。可以通过getCause()方法获取引起当前异常的根本原因。

    try {throw new IOException("File not found");
    } catch (IOException e) {throw new RuntimeException("Failed to read file", e); // 将 IOException 作为 RuntimeException 的根本原因
    }
    

    使用e.getCause()可以获取到原始的异常对象,从而追踪到真正的错误源。

  2. 多线程中的异常处理: 在多线程编程中,异常处理稍显复杂。每个线程都有自己的执行栈,因此在每个线程中都可能发生异常。Java提供了Thread.UncaughtExceptionHandler接口来处理未捕获的线程异常。

    Thread thread = new Thread(() -> {// 可能抛出异常的代码
    });
    thread.setUncaughtExceptionHandler((t, e) -> {System.out.println("线程 " + t.getName() + " 抛出了异常:" + e.getMessage());
    });
    thread.start();
    

    通过设置未捕获异常处理器,我们可以对线程中的异常进行集中处理,而不会让整个应用崩溃。

12.Java 8 引入的异常流处理

在Java 8中,引入了流式API(Stream API),这使得在进行流操作时,异常处理变得更加重要。流中的方法(如map()filter()等)通常要求无异常的输入,但是你可能会遇到需要在流中处理异常的场景。

一种常见的方式是通过try-catch包装流中的异常:

List<String> data = Arrays.asList("1", "2", "abc", "4");List<Integer> result = data.stream().map(str -> {try {return Integer.parseInt(str);} catch (NumberFormatException e) {return null;  // 处理异常,返回null}}).filter(Objects::nonNull).collect(Collectors.toList());System.out.println(result);  // 输出:[1, 2, 4]

在这种情况下,我们使用了map()来处理每个元素可能发生的NumberFormatException异常,并返回null值,最后通过filter()去掉null值。

13.资源管理与自动关闭(Java 7引入的AutoCloseable)

从Java 7开始,引入了自动资源管理(ARM),即try-with-resources语句,专门用于处理需要关闭的资源(如文件、数据库连接等)。资源实现了AutoCloseable接口,保证无论是否发生异常,都会自动关闭资源。

try (FileReader fr = new FileReader("file.txt")) {// 读取文件
} catch (IOException e) {e.printStackTrace();
} // FileReader 会在此自动关闭,无论是否发生异常

这种方式能够确保即使在异常发生时,资源也能正确地被释放,避免了资源泄漏问题。

相关文章:

[Java]异常

在程序运行时&#xff0c;如果遇到问题&#xff08;比如除以零、文件找不到等&#xff09;&#xff0c;程序会发生异常。异常就像是程序的“错误提醒”&#xff0c;当程序运行中出错时&#xff0c;它会停止&#xff0c;给出一个错误信息。我们可以通过异常处理来控制这些错误&a…...

【C++语言】卡码网语言基础课系列----13. 链表的基础操作I

文章目录 背景知识链表1、虚拟头节点(dummyNode)2、定义链表节点3、链表的插入 练习题目链表的基础操作I具体代码实现 小白寄语诗词共勉 背景知识 链表 与数组不同&#xff0c;链表的元素存储可以是连续的&#xff0c;也可以是不连续的&#xff0c;每个数据除了存储本身的信息…...

Vue.js组件开发-实现图片浮动效果

使用Vue实现图片浮动效果 实现思路 将使用Vue的单文件组件&#xff08;.vue&#xff09;来实现图片浮动效果。主要思路是通过CSS的transform属性结合JavaScript的定时器来改变图片的位置&#xff0c;从而实现浮动效果。 代码实现 <template><!-- 定义一个包含图片…...

自制Windows系统(十一、Windows11GUI)

开源地址&#xff1a;下载&#xff08;Work(Windows11gui).img&#xff09; 上图 部分代码&#xff1a; void init_screen8(char *vram, int x, int y) { int *fat; unsigned char c; struct MEMMAN *memman (struct MEMMAN *) MEMMAN_ADDR; boxfill8(vram, x, 136, 0, …...

索罗斯的“反身性”(Reflexivity)理论:市场如何扭曲现实?(中英双语)

索罗斯的“反身性”&#xff08;Reflexivity&#xff09;理论&#xff1a;市场如何扭曲现实&#xff1f; 一、引言&#xff1a;市场是镜子&#xff0c;还是哈哈镜&#xff1f; 在传统经济学中&#xff0c;市场通常被认为是一个理性、有效的反映现实的系统。按照经典经济学理论…...

力扣257. 二叉树的所有路径(遍历思想解决)

Problem: 257. 二叉树的所有路径 文章目录 题目描述思路复杂度Code 题目描述 思路 遍历思想(利用二叉树的先序遍历) 利用先序遍历的思想&#xff0c;我门用一个List变量path记录当前先序遍历的节点&#xff0c;当遍历到根节点时&#xff0c;将其添加到另一个List变量res中&…...

使用朴素贝叶斯对散点数据进行分类

本文将通过一个具体的例子&#xff0c;展示如何使用 Python 和 scikit-learn 库中的 GaussianNB 模型&#xff0c;对二维散点数据进行分类&#xff0c;并可视化分类结果。 1. 数据准备 假设我们有两个类别的二维散点数据&#xff0c;每个类别包含若干个点。我们将这些点分别存…...

如何实现滑动列表功能

文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了沉浸式状态栏相关的内容&#xff0c;本章回中将介绍SliverList组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1 概念介绍 我们在这里介绍的SliverList组件是一种列表类组件&#xff0c;类似我们之前介…...

计算机网络一点事(22)

地址解析协议ARP ARP&#xff1a;查询Mac地址 ARP表&#xff08;ARP缓存&#xff09;&#xff1a;记录映射关系&#xff0c;一个数据结构&#xff0c;定期更新ARP表 过程&#xff1a;请求分组&#xff0c;响应分组 动态主机配置协议DHCP 分配IP地址&#xff0c;配置默认网关…...

C# 语言基础全面解析

.NET学习资料 .NET学习资料 .NET学习资料 一、引言 C# 是一种功能强大、面向对象且类型安全的编程语言&#xff0c;由微软开发&#xff0c;广泛应用于各种类型的软件开发&#xff0c;从桌面应用、Web 应用到游戏开发等领域。本文将全面介绍 C# 语言的基础知识&#xff0c;帮…...

[原创](Modern C++)现代C++的关键性概念: 流格式化

常用网名: 猪头三 出生日期: 1981.XX.XX 企鹅交流: 643439947 个人网站: 80x86汇编小站 编程生涯: 2001年~至今[共24年] 职业生涯: 22年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、Delphi、XCode、Eclipse、C Bui…...

《数据可视化新高度:Graphy的AI协作变革》

在数据洪流奔涌的时代&#xff0c;企业面临的挑战不再仅仅是数据的收集&#xff0c;更在于如何高效地将数据转化为洞察&#xff0c;助力决策。Graphy作为一款前沿的数据可视化工具&#xff0c;凭借AI赋能的团队协作功能&#xff0c;为企业打开了数据协作新局面&#xff0c;重新…...

C++并发:设计无锁数据结构

只要摆脱锁&#xff0c;实现支持安全并发访问的数据结构&#xff0c;就有可能解决大粒度锁影响并发程度以及错误的加锁方式导致死锁的问题。这种数据结构称为无锁数据结构。 在了解本文时&#xff0c;务必读懂内存次序章节。 在设计无锁数据结构时&#xff0c;需要极为小心谨…...

蓝桥杯刷题DAY2:二维前缀和 一维前缀和 差分数组

闪耀的灯光 &#x1f4cc; 题目描述 蓝桥公园是一个适合夜间散步的好地方&#xff0c;公园可以被视为由 n m 个矩形区域构成。每个区域都有一盏灯&#xff0c;初始亮度为 a[i][j]。 小蓝可以选择一个大的矩形区域&#xff0c;并按下开关一次&#xff0c;这将使得该区域内每盏…...

雷电等基于VirtualBox的Android模拟器映射串口和测试CSerialPort串口功能

雷电等基于VirtualBox的Android模拟器映射串口和测试CSerialPort串口功能 1. 修改VirtualBox配置文件映射串口 模拟器配置文件vms/leidian0/leidian.vbox。 在UART标签下增加(修改完成后需要将leidian.vbox修改为只读) <Port slot"1" enabled"true"…...

四、jQuery笔记

(一)jQuery概述 jQuery本身是js的一个轻量级的库,封装了一个对象jQuery,jquery的所有语法都在jQuery对象中 浏览器不认识jquery,只渲染html、css和js代码,需要先导入jQuery文件,官网下载即可 jQuery中文说明文档:https://hemin.cn/jq/ (二)jQuery要点 1、jQuery对象 …...

流浪 Linux: 外置 USB SSD 安装 ArchLinux

注: ArchLinux 系统为滚动更新, 变化很快, 所以本文中的安装方法可能很快就过时了, 仅供参考. 实际安装时建议去阅读官方文档. 最近, 突然 (也没有那么突然) 有了一大堆 PC: 4 个笔记本, 2 个台式主机 (M-ATX 主板), 1 个小主机 (迷你主机). 嗯, 多到用不过来. 但是, 窝又不能…...

1.For New TFLite Beginner

一、 Getting Started for ML Beginners This document explains how to use machine learning to classify (categorize) Iris flowers by species. This document dives deeply into the TensorFlow code to do exactly that, explaining ML fundamentals along the way. If…...

吊打同类软件免费又可批量使用

聊一聊 对于经常用到席卡的人来说&#xff0c;每次打印都觉得麻烦&#xff0c;要是有个软件&#xff0c;直接输入名称就能打印就好了。 这不&#xff0c;只要你想&#xff0c;就肯定能实现&#xff1b;如果没实现&#xff0c;就说明你不够想。 这个软件我测试了下&#xff0…...

MiniMind——跑通项目

文章目录 &#x1f4cc; Quick Start Train MiniMind (ModelScope) # step 1 git clone https://huggingface.co/jingyaogong/minimind-v1# step 2 python 2-eval.py或者启动streamlit&#xff0c;启动网页聊天界面 「注意」需要python>3.10&#xff0c;安装 pip install s…...

label-studio的使用教程(导入本地路径)

文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图&#xff0c;该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序&#xff0c;确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数&#xff0c;分别表示n 和 e 的值&#xff08;1…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)

本期内容并不是很难&#xff0c;相信大家会学的很愉快&#xff0c;当然对于有后端基础的朋友来说&#xff0c;本期内容更加容易了解&#xff0c;当然没有基础的也别担心&#xff0c;本期内容会详细解释有关内容 本期用到的软件&#xff1a;yakit&#xff08;因为经过之前好多期…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

网站指纹识别

网站指纹识别 网站的最基本组成&#xff1a;服务器&#xff08;操作系统&#xff09;、中间件&#xff08;web容器&#xff09;、脚本语言、数据厍 为什么要了解这些&#xff1f;举个例子&#xff1a;发现了一个文件读取漏洞&#xff0c;我们需要读/etc/passwd&#xff0c;如…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.

ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #&#xff1a…...

怎么让Comfyui导出的图像不包含工作流信息,

为了数据安全&#xff0c;让Comfyui导出的图像不包含工作流信息&#xff0c;导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo&#xff08;推荐&#xff09;​​ 在 save_images 方法中&#xff0c;​​删除或注释掉所有与 metadata …...

STM32---外部32.768K晶振(LSE)无法起振问题

晶振是否起振主要就检查两个1、晶振与MCU是否兼容&#xff1b;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容&#xff08;CL&#xff09;与匹配电容&#xff08;CL1、CL2&#xff09;的关系 2. 如何选择 CL1 和 CL…...

【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制

目录 节点的功能承载层&#xff08;GATT/Adv&#xff09;局限性&#xff1a; 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能&#xff0c;如 Configuration …...