Java异常_18

异常

异常(Exception)和错误(Error)都是Throwable类的子类,但它们有不同的用途和继承关系

Throwable
├── Error
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── ...
└── Exception
    ├── 运行时异常(RuntimeException)
    │   ├── NullPointerException
    │   ├── ArrayIndexOutOfBoundsException
    │   └── ...
    └── 编译时异常()
        ├── IOException
        ├── SQLException
        └── ...

错误

  • 表示程序无法处理的严重系统级问题,通常由 JVM 或底层资源故障引发,如内存溢出(OutOfMemoryError)、栈溢出(StackOverflowError)等
  • 属于 Throwable 类的子类,但无法通过代码捕获或处理
  • 通常由 JVM 抛出,程序无法主动恢复,会导致应用终止

异常

  • 异常(Exception)是Java中用于处理程序运行时错误的一种机制。异常处理使得程序在遇到错误时能够优雅地恢复或终止,而不是直接崩溃。Java的异常处理机制基于抛出(throw)和捕获(catch)异常
  • 程序运行时可预见或可恢复的非致命问题,如文件找不到(FileNotFoundException)、空指针(NullPointerException)等
  • 继承自 Throwable,但属于应用程序级别的错误,可通过 try-catch 处理
  • 分为 编译时异常(Checked Exception) 和 运行时异常(RuntimeException)

编译时异常

  • 编译时异常是Java中一种在编译时强制要求处理的异常。编译器会检查这些异常是否被正确处理(捕获或声明抛出),如果没有处理,代码将无法通过编译

  • 常见的编译时异常

    1. IOException,表示输入输出操作中的异常,通常与文件读写、网络通信等相关

    2. SQLException,表示数据库操作中的异常,通常与JDBC操作相关

    3. ClassNotFoundException,表示类未找到异常,通常与类加载相关

      public class Main {
          public static void main(String[] args) {
              try {
                  Class.forName("com.example.NonExistentClass");
              } catch (ClassNotFoundException e) {
                  System.out.println("Class not found: " + e.getMessage());
              }
          }
      }
      
  • 编译时异常的处理方式

    编译时异常必须被处理,否则代码将无法通过编译。处理编译时异常的方式有两种

    • 捕获异常:使用try-catch语句捕获并处理异常

      import java.io.FileInputStream;
      import java.io.FileNotFoundException;
      
      public class Main {
          public static void main(String[] args) {
              try {
                  FileInputStream file = new FileInputStream("nonexistent.txt");
              } catch (FileNotFoundException e) {
                  System.out.println("File not found: " + e.getMessage());
              }
          }
      }
      
    • 声明抛出异常:使用throws关键字声明方法可能抛出的异常,将异常交给调用者处理

      import java.io.FileInputStream;
      import java.io.FileNotFoundException;
      
      public class Main {
          public static void main(String[] args) throws FileNotFoundException {
              FileInputStream file = new FileInputStream("nonexistent.txt");
          }
      }
      

运行时异常

  • 运行时异常是Java中一种在运行时才会被检测到的异常。与编译时异常不同,运行时异常不需要在代码中显式捕获或声明抛出,编译器不会强制要求处理这些异常

  • 运行时异常通常表示程序逻辑错误(如空指针、数组越界等),而不是外部资源或环境问题

  • 常见运行时异常

    1. NullPointerException,表示尝试访问空对象的成员(如方法或字段)

    2. ArrayIndexOutOfBoundsException,表示数组访问越界

    3. ArithmeticException,表示算术运算错误(如除零)

    4. IllegalArgumentException,表示传递给方法的参数不合法

      public class Main {
          public static void main(String[] args) {
              setAge(-1); // 抛出 IllegalArgumentException
          }
      
          public static void setAge(int age) {
              if (age < 0) {
                  throw new IllegalArgumentException("Age cannot be negative");
              }
          }
      }
      
    5. ClassCastException,表示类型转换错误

  • 运行时异常的处理方式

    运行时异常不需要在代码中显式捕获或声明抛出,但可以通过以下方式处理

    • 捕获异常:使用try-catch语句捕获并处理异常
    • 避免异常:通过代码逻辑避免异常的发生

自定义异常

  1. 自定义编译时异常

    自定义编译时异常需要继承 Exception 类。编译器会强制要求处理这些异常

  2. 自定义运行时异常

    运行时异常需要继承 RuntimeException 类。编译器不会强制要求处理这些异常

    一般定义为运行时异常

    // 自定义运行时异常
    class MyUncheckedException extends RuntimeException {
        // 无参构造函数
        public MyUncheckedException() {
            super();
        }
    
        // 带消息的构造函数
        public MyUncheckedException(String message) {
            super(message);
        }
    
        // 带消息和原因的构造函数
        public MyUncheckedException(String message, Throwable cause) {
            super(message, cause);
        }
    
        // 带原因的构造函数
        public MyUncheckedException(Throwable cause) {
            super(cause);
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            validateAge(-1); // 调用可能抛出异常的方法
        }
    
        // 方法抛出自定义运行时异常
        public static void validateAge(int age) {
            if (age < 0) {
                throw new MyUncheckedException("Age cannot be negative");
            }
        }
    }
    

异常处理方式

  1. try-catch-finally 语句

    • try-catch-finally 是Java中最常用的异常处理机制

      try {
          // 可能抛出异常的代码
      } catch (ExceptionType e) {
          // 捕获并处理异常
      } finally {
          // 无论是否发生异常,都会执行的代码
      }
      
    • 避免空的 catch 块,空的catch块会隐藏错误,导致调试困难。即使不需要处理异常,至少应该记录日志或输出错误信息

    • try 块必须至少有一个 catch 块或一个 finally 块,否则会编译错误

    • 无论是否发生异常,finally块中的代码都会执行。通常用于释放资源(如关闭文件、数据库连接等)

    • catch语句可以有多个

    • 多个catch语句的顺序非常重要,必须从具体到通用。也就是说,子类异常(更具体的异常)应该放在父类异常(更通用的异常)之前

      try {
          // 可能抛出异常的代码
      } catch (FileNotFoundException e) {
          System.out.println("File not found: " + e.getMessage());
      } catch (IOException e) {
          System.out.println("IO error: " + e.getMessage());
      } catch (Exception e) {
          System.out.println("Caught exception: " + e.getMessage());
      }
      
    • 从Java 7开始,可以在一个catch块中捕获多个异常类型,用|分隔。这样可以避免重复代码

      try {
          // 可能抛出异常的代码
      } catch (FileNotFoundException | IOException e) {
          System.out.println("File or IO error: " + e.getMessage());
      } catch (Exception e) {
          System.out.println("General exception: " + e.getMessage());
      }
      
    • 如果如果抛出的异常类型与catch块声明的异常类型不匹配,catch块无法捕获异常,异常会向上传递,最终由JVM处理,程序终止并打印异常堆栈信息

    • 由于上一条原因所以会有try-finally语句,不管有没有成功运行都要执行finally语句块中的内容

    try-with-resources 的结构

    • try-with-resources 是Java 7引入的一种语法结构,用于自动管理资源(如文件、数据库连接、网络连接等)。它的主要目的是简化资源管理代码,确保资源在使用完毕后能够被正确关闭,避免资源泄漏

    • try-with-resources 语句会自动调用资源的 close() 方法,无论 try 块中的代码是否抛出异常。这比传统的 try-catch-finally 结构更加简洁和安全

    • 基本结构如下

      try (资源声明) {
          // 使用资源的代码
      } catch (异常类型 e) {
          // 处理异常
      } finally {
          // 可选的 finally 块
      }
      
      • 资源声明:在 try 后面的括号中声明并初始化资源。可以声明多个资源,用分号分隔
      • 资源类型:资源必须实现 AutoCloseable 或 Closeable 接口
      • 自动关闭:无论 try 块中的代码是否抛出异常,资源都会在 try 块结束时自动关闭
      import java.io.FileInputStream;
      import java.io.FileOutputStream;
      import java.io.IOException;
      
      public class Main {
          public static void main(String[] args) {
              try (
                  FileInputStream input = new FileInputStream("input.txt");
                  FileOutputStream output = new FileOutputStream("output.txt")
              ) {
                  int data;
                  while ((data = input.read()) != -1) {
                      output.write(data);
                  }
              } catch (IOException e) {
                  System.out.println("Caught exception: " + e.getMessage());
              }
          }
      }
      
  2. throw 关键字

    • throw 关键字用于手动抛出异常。通常用于在满足某些条件时抛出异常

      public class Main {
          public static void main(String[] args) {
              try {
                  throw new RuntimeException("Manual exception");
              } catch (RuntimeException e) {
                  System.out.println("Caught exception: " + e.getMessage());
              }
          }
      }
      
  3. throws 关键字

    1. 声明方法可能抛出的异常:告诉调用者,该方法可能会抛出某些异常,调用者需要处理这些异常(捕获或继续抛出)

    2. throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父亲类型

    3. 如果抛出的是父亲类型,那么在调用该方法时catch也要是父类类型或者更上面的类型

      返回类型 方法名(参数列表) throws 异常类型1, 异常类型2, ... {
          // 方法体
      }
      
      public class FileReader {
          // 在定义方法时声明可能抛出的异常
          public static void readFile(String filename) throws FileNotFoundException {
              FileInputStream file = new FileInputStream(filename);
              System.out.println("File opened successfully");
          }
      }
      
    4. 将异常传递给调用者:如果方法内部发生了异常,但没有捕获和处理,可以使用throws将异常抛给调用者

      public class Main {
          public static void main(String[] args) throws FileNotFoundException {
              FileReader.readFile("nonexistent.txt"); // 调用可能抛出异常的方法
          }
      }
      

    异常抑制

    • 在传统的 try-catch-finally 结构中,如果在 try 块中抛出异常,同时在 finally 块中也抛出异常,那么 finally 块中的异常会覆盖 try 块中的异常,导致 try 块中的异常信息丢失

    • 在下面的例子中,最终只会抛出 finally 块中的异常,try 块中的异常信息会丢失

      public class Main {
          public static void main(String[] args) {
              try {
                  throw new RuntimeException("Exception in try block");
              } finally {
                  throw new RuntimeException("Exception in finally block");
              }
          }
      }
      
      Exception in thread "main" java.lang.RuntimeException: Exception in finally block
          at Main.main(Main.java:6)
      

    try-with-resources 的异常抑制机制

    • 如果 try 块中抛出异常,同时资源的 close() 方法也抛出异常,那么 try 块中的异常会被正常抛出,而 close() 方法抛出的异常会被抑制
    • 被抑制的异常可以通过 Throwable.getSuppressed() 方法获取

    例如:

    1. 以下是一个自定义资源类,它在 close() 方法中抛出异常

      class MyResource implements AutoCloseable {
          @Override
          public void close() throws Exception {
              throw new Exception("Exception in close()");
          }
      }
      
    2. 使用 try-with-resources

      在 try-with-resources 语句中使用这个资源,并在 try 块中抛出异常

      public class Main {
          public static void main(String[] args) {
              try (MyResource resource = new MyResource()) {
                  throw new RuntimeException("Exception in try block");
              } catch (Exception e) {
                  System.out.println("Caught exception: " + e.getMessage());
                  for (Throwable suppressed : e.getSuppressed()) {
                      System.out.println("Suppressed exception: " + suppressed.getMessage());
                  }
              }
          }
      }
      
      Caught exception: Exception in try block
      Suppressed exception: Exception in close()
      
      • try 块中抛出了 RuntimeException("Exception in try block")。
      • close() 方法抛出了 Exception("Exception in close()")
      • try 块中的异常会被正常抛出,而 close() 方法抛出的异常会被抑制
    3. Throwable.getSuppressed() 方法

      • Throwable.getSuppressed() 方法返回一个数组,包含所有被抑制的异常。这些异常通常是在 try-with-resources 语句中,资源关闭时抛出的异常

        for (Throwable suppressed : e.getSuppressed()) {
            System.out.println("Suppressed exception: " + suppressed.getMessage());
        }
        
posted @ 2025-03-03 21:55  QAQ001  阅读(32)  评论(0)    收藏  举报