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

[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());
        }
    }

    // 处理文件的方法,可能会抛出 IOException
    public 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 {
            // 打开一个不存在的文件,会抛出 IOException
            FileReader file = new FileReader("nonexistentfile.txt");
            BufferedReader reader = new BufferedReader(file);
            reader.read();
            reader.close();
        } catch (IOException e) {
            // 捕获并处理 IOException
            System.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,最大索引为 2
            arr[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 file
    at ExceptionChainingDemo.processFile(ExceptionChainingDemo.java:9)
    at ExceptionChainingDemo.main(ExceptionChainingDemo.java:4)
Caused by: java.io.IOException: File not found
    at 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() {
        // 模拟抛出一个 RuntimeException
        throw 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 method1
    at PrintStackTraceExample.method1(PrintStackTraceExample.java:10)
    at PrintStackTraceExample.main(PrintStackTraceExample.java:5)
Caused by: java.lang.Exception: Error in method2
    at 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 抛出 Exception
    public 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 methodB
    at 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();  // 可能抛出 IOException
            connectToDatabase();  // 可能抛出 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 会在此自动关闭,无论是否发生异常

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


http://www.kler.cn/a/530122.html

相关文章:

  • Ubuntu 下 nginx-1.24.0 源码分析 main函数 — ngx_cdecl 宏
  • SOME/IP--协议英文原文讲解3
  • 基于springboot+vue的哈利波特书影音互动科普网站
  • 【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.12 连续数组:为什么contiguous这么重要?
  • 深入解析:一个简单的浮动布局 HTML 示例
  • 【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.9 广播陷阱:形状不匹配的深层隐患
  • GAMES101学习笔记(六):Geometry 几何(基本表示方法、曲线与曲面、网格处理)
  • 海外问卷调查渠道查,如何影响企业的运营
  • Rust 变量特性:不可变、和常量的区别、 Shadowing
  • 零基础学习书生.浦语大模型-入门岛
  • IM 即时通讯系统-50-[特殊字符]cim(cross IM) 适用于开发者的分布式即时通讯系统
  • 封装常用控制器
  • Java NIO全面详解
  • FreeRTOS学习 --- 消息队列
  • 【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.11 视图与副本:内存优化的双刃剑
  • leetcode 2856. 删除数对后的最小数组长度
  • 【TypeScript】扩展:装饰器
  • 在Arm芯片苹果Mac系统上通过homebrew安装多版本mysql并解决各种报错,感谢deepseek帮助解决部分问题
  • Python 原子操作:使用 `atomic` 模块保证线程安全
  • web前端12--表单和表格
  • 科技快讯 | 领英“隐私风波”告一段落;华为余承东智驾 1345 公里返工,称智界 R7 打赢“鸡蛋保卫战”;谷歌翻译将增“提问”功能
  • 利用Spring Batch简化企业级批处理应用开发
  • 【漫话机器学习系列】075.隐含层(Hidden Layer)
  • Git如何避免推送.idea文件夹
  • 使用 vllm 搭建推理加速大模型服务
  • OpenAI 实战进阶教程 - 第二节:生成与解析结构化数据:从文本到表格