异常

异常

1.什么是异常

在Java中,异常(Exception)是指程序执行过程中可能出现的不正常情况或错误。它是一个事件,会干扰程序的正常执行流程,并可能导致程序出现错误或崩溃。异常在Java中是以对象的形式表示的,这些对象是从java.lang.Throwable类或其子类派生而来。

定义:异常是在程序执行期间发生的事件,它中断正在执行的程序的正常指令流。为了能够及时有效地处理程序中的运行错误,Java专门引入了异常类。

2.Java异常类结构图

Java异常类结构是Java语言异常处理框架的基础,它允许程序在遇到预期或意外情况时,能够优雅地处理这些错误,而不是立即终止运行。Java异常类结构的核心是java.lang.Throwable类,它是所有异常和错误的根类。

Throwable类及其子类

  • Throwable:所有异常和错误的超类。它有两个直接子类:ErrorException

Error类

  • Error:表示程序无法恢复的严重错误,如系统崩溃、内存溢出等。这类错误通常不需要程序处理,因为它们通常是不可控的系统级错误。常见的Error子类包括OutOfMemoryErrorStackOverflowError等。

Exception类

  • Exception:表示程序本身可以处理的异常。它进一步分为两大类:运行时异常(Unchecked Exception)和非运行时异常(Checked Exception,也称为编译时异常)。

3.Exception分类

3.1.运行时异常

  • 运行时异常(RuntimeException及其子类):在程序运行时可能发生的异常,这些异常通常是由于程序中的逻辑错误导致的,而不是由外部因素(如文件不存在、网络问题等)引起的。Java编译器不要求程序必须显式地处理这些异常,但开发者仍然应该尽力避免它们的发生,以提高程序的健壮性和可靠性。

    常见的运行时异常:

    1. NullPointerException(空指针异常):
      • 当尝试访问或操作一个未初始化(即为null)的对象时抛出。
      • 常见场景包括调用null对象的属性或方法。
    2. ArrayIndexOutOfBoundsException(数组索引越界异常):
      • 当尝试访问数组的索引超出其有效范围时抛出。
      • 例如,访问数组的负索引或超出数组长度的索引。
    3. ArithmeticException(算术异常):
      • 在进行算术运算时,如果违反了运算规则(如整数除零),则抛出此异常。
    4. ClassCastException(类型转换异常):
      • 当尝试将对象强制转换为不是其实例的子类时抛出。
      • 例如,将String对象转换为Integer类型。
    5. IllegalArgumentException(非法参数异常):
      • 当向方法传递了非法或不适当的参数时抛出。
      • 例如,向方法传递了负数的数组长度。
    6. IllegalStateException(非法状态异常):
      • 当对象的状态不允许进行某些操作时抛出。
      • 例如,在已经关闭的流上调用读写方法。
    7. UnsupportedOperationException(不支持的操作异常):
      • 当尝试调用对象上不支持的方法时抛出。
      • 例如,在不可修改的集合上调用修改方法。
    8. ConcurrentModificationException(并发修改异常):
      • 在多线程环境下,当某个线程在遍历集合的同时,另一个线程修改了该集合,导致遍历抛出此异常。
    9. NumberFormatException(数字格式异常):
      • 当尝试将字符串转换为数字时,如果字符串的格式不正确,则抛出此异常。
      • 例如,将非数字的字符串转换为整数。
    10. SecurityException(安全异常):
      • 当安全管理器存在且其checkPermission方法不允许进行某些操作时抛出。

3.2.受检查异常

  • 受检查异常(Checked Exception):受检查异常是指在编译时期就需要程序员进行处理的异常。这些异常通常是由外部因素引起的,如文件操作、数据库连接等,它们的发生是可以预见的,并且程序应该提供相应的处理逻辑来应对。这类异常在编译时必须显式处理,即要么在方法内部使用try-catch语句捕获并处理,要么在方法签名上通过throws关键字声明可能会抛出的异常。常见的非运行时异常包括IOExceptionSQLException等。

    常见的受检查异常类:

    1. IOException:输入输出异常,表示在输入输出过程中发生的错误。这是最常见的受检查异常之一,几乎所有的文件读写、网络通信等操作都可能抛出这个异常。
    2. SQLException:SQL异常,表示数据库操作中发生的错误。例如,执行SQL语句时语法错误、连接数据库失败等。
    3. ClassNotFoundException:类未找到异常,当尝试加载一个类但找不到其定义时抛出。这通常发生在动态加载类时,如使用Class.forName()方法。
    4. FileNotFoundException:文件未找到异常,当试图打开一个不存在的文件时抛出。这个异常是IOException的子类,但它特别用于表示文件未找到的情况。
    5. 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块中使用了returnbreakcontinue语句,并且存在finally块,那么finally块会在这些语句执行之前执行。但是,finally块中的return语句会覆盖trycatch块中的return语句(如果有的话)。
  • 尽量避免在finally块中抛出异常,因为这可能会掩盖原始异常。如果确实需要在finally块中执行可能抛出异常的代码,请确保你能够妥善处理这些异常,或者至少将它们记录到日志中。

4.3.异常传播

在Java中,异常传播(Exception Propagation)是指当一个方法内部发生异常时,这个异常可以被抛出到调用该方法的代码中,进而可以继续向上传播,直到被捕获或程序终止。异常传播是Java异常处理机制的一个重要方面,它允许开发者在更高的层次上处理异常,从而避免在每个可能抛出异常的代码块中都进行异常处理。

异常传播的基本规则

  1. 抛出异常:当一个方法内部发生异常时,可以使用throw关键字抛出异常。如果这个方法没有捕获这个异常,那么它就会沿着调用栈向上传播。
  2. 捕获异常:在异常传播的路径上,如果某个方法使用try-catch语句捕获了异常,那么异常就不会继续向上传播。捕获异常后,可以在catch块中处理异常,或者再次抛出异常(可以使用throw关键字,或者通过throw new Exception(...)抛出一个新的异常)。
  3. 声明抛出异常:如果一个方法可能抛出受检查异常(checked exception),但是该方法内部并没有捕获这个异常,那么这个方法就必须在方法签名上使用throws关键字声明这个异常。这样,调用这个方法的代码就必须处理这个异常,要么通过try-catch语句捕获,要么继续向上抛出。
  4. 异常传播直到被捕获:异常会一直沿着调用栈向上传播,直到被某个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内部抛出了一个异常,由于method2catch块中使用了throw e;,这个异常被重新抛出。然后,这个异常沿着调用栈向上传播到method1,但是method1并没有捕获这个异常,而是使用throws关键字声明了它。因此,这个异常继续向上传播到main方法,并在那里被捕获和处理。

注意事项

  • 合理地使用异常传播可以使代码更加清晰和易于维护,但是过度使用或滥用异常传播(如将异常用于控制程序流程)可能会导致代码难以理解和调试。
  • 在设计API时,应该仔细考虑哪些异常是应该被声明为受检查异常(checked exception),哪些应该是非受检查异常(unchecked exception,即RuntimeException及其子类)。受检查异常要求调用者显式地处理异常,这有助于编写更健壮的代码;但是,如果过多地使用受检查异常,可能会使API的使用变得繁琐。

5.自定义异常

在Java中,自定义异常是通过继承Java的异常类(如ExceptionRuntimeException及其子类)来实现的。自定义异常允许你定义具有特定含义的异常,这些异常可以更好地反映你的应用程序中可能发生的错误情况。

继承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());  
}
posted @   tubby233  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示