JAVA基础【异常处理】

Java 异常处理详解

一、异常处理的重要性
  • 错误分类 :程序运行中可能出现用户输入错误(如NumberFormatException)、资源缺失(如FileNotFoundException)、系统级错误(如内存耗尽、网络中断)等。

  • 处理方式对比

    • 错误码(如 C 语言) :通过返回整数表示错误,需手动枚举和判断,代码繁琐。
    • Java 异常机制 :通过class封装错误类型,可在任意位置抛出,上层统一捕获,分离错误处理逻辑。
二、Java 异常继承体系
plaintext 复制代码
Object
├─ Throwable(所有异常/错误的根)
│  ├─ Error(严重错误,程序无法处理,无需捕获)
│  │  例:`OutOfMemoryError`(内存耗尽)、`StackOverflowError`(栈溢出)、`NoClassDefFoundError`(类缺失)
│  └─ Exception(可处理的运行时错误)
│     ├─ RuntimeException(非受检异常,编译器不强制捕获)
│     │  例:`NullPointerException`(空指针)、`IndexOutOfBoundsException`(索引越界)、`IllegalArgumentException`(非法参数)
│     └─ 非RuntimeException(受检异常,编译器强制要求捕获或声明抛出)
│        例:`IOException`(IO错误)、`UnsupportedEncodingException`(编码不支持)、`SQLException`(数据库错误)
三、受检异常(Checked Exception)与非受检异常(Unchecked Exception)
特性 受检异常(Exception 非 Runtime 子类) 非受检异常(RuntimeException 及其子类 / Error)
编译器检查 强制要求try...catch捕获或throws声明 不强制,可自由选择是否处理
处理责任 调用方必须处理 通常由程序员修复代码逻辑错误,而非运行时处理
常见场景 外部资源操作(文件、网络、数据库) 程序逻辑错误(空指针、越界、非法参数等)
四、异常处理核心语法:try...catchthrows
  1. try...catch捕获异常

    • 将可能抛出异常的代码放入try块,通过多个catch块捕获不同类型的异常(子类异常需放在父类之前)。

    • 示例

      java 复制代码
      try {
          String data = readFile("data.txt"); // 可能抛出FileNotFoundException
      } catch (FileNotFoundException e) {
          System.out.println("文件未找到:" + e.getMessage());
          e.printStackTrace(); // 打印异常栈轨迹
      } catch (IOException e) { // 处理更宽泛的异常
          System.out.println("IO错误:" + e);
      }
  2. throws声明异常

    • 方法定义时用throws声明可能抛出的受检异常,将处理责任交给调用方。

    • 示例

      arduino 复制代码
      static byte[] toGBK(String s) throws UnsupportedEncodingException {
          return s.getBytes("GBK"); // 声明抛出受检异常,调用方必须处理
      }
  3. 注意事项

    • 受检异常必须处理:未捕获或声明受检异常会导致编译错误。
    • 避免空捕获 :捕获异常后至少记录日志(如printStackTrace()),避免静默忽略错误。
    • main方法作为最后屏障 :可声明throws Exception直接终止程序,或在main中统一捕获所有异常。

捕获异常

一、核心概念

在 Java 中,try...catch机制用于捕获可能抛出的异常。将可能引发异常的代码置于try块中,通过catch块捕获对应的Exception及其子类。

二、多 catch 语句
  1. 执行逻辑 :JVM 按顺序匹配catch块,匹配成功后执行对应代码块,不再继续匹配。

  2. 顺序要求:子类异常必须写在父类异常前面,否则子类异常永远无法被捕获。

    • 错误示例:catch(IOException e)后接catch(UnsupportedEncodingException e)(子类在后,无法捕获)。
    • 正确示例:先子类UnsupportedEncodingException,再父类IOException
三、finally 语句
  1. 作用 :无论是否发生异常,finally块中的代码都会执行,用于资源清理等必须执行的逻辑。

  2. 特点

    • 非必需,可省略。
    • 始终最后执行:正常执行完try后执行;发生异常时,执行完匹配的catch后执行。
    • 可单独使用try...finally(无需catch,但需方法声明抛出异常)。
四、捕获多种异常
  • 场景 :处理逻辑相同但无继承关系的异常(如IOExceptionNumberFormatException)。

  • 语法 :使用|合并多个异常类型,简化代码。

    java 复制代码
    catch(IOException | NumberFormatException e) {
        // 统一处理逻辑
    }

抛出异常

一、异常的传播
  1. 传播机制 :当方法抛出异常且未被捕获时,异常会向上层调用链传播,直至被try...catch捕获。
  2. 调试工具 :通过printStackTrace()打印异常调用栈,显示异常发生的方法调用层次及源码行号,便于定位问题。示例中NumberFormatException的栈信息清晰展示了从main()Integer.parseInt()的调用路径。
二、抛出异常的核心要点
  1. 基本步骤

    • 创建Exception实例(如new NullPointerException())。
    • 使用throw语句抛出,可合并为一行代码(throw new NullPointerException();)。
  2. 异常转换与原始信息保留

    • 捕获异常后若需抛出新异常,应在新异常构造器中传入原始异常(如throw new IllegalArgumentException(e)),通过Caused by保留完整栈信息,避免丢失 "第一案发现场"。
    • 最佳实践:始终保留原始异常,确保调用链信息完整。
  3. finally语句的执行规则

    • 即使在trycatch中抛出异常,finally仍会执行,JVM 会先执行finally再抛出异常。
三、异常屏蔽与处理
  1. 屏蔽现象 :若finally中抛出新异常,catch中准备抛出的异常会被屏蔽(称为Suppressed Exception)。
  2. 保留被屏蔽异常 :通过Throwable.addSuppressed(origin)将原始异常添加到新异常中,可通过getSuppressed()获取。
  3. 最佳实践 :绝大多数情况下,避免在finally中抛出异常,减少调试复杂度。
四、异常信息的重要性
  • 提问或调试时,必须提供完整的异常栈信息(包括Caused bySuppressed部分),否则难以定位问题根源。
五、核心总结
关键点 说明
异常传播 异常沿调用链向上抛出,printStackTrace()打印栈信息辅助定位。
抛出异常 throw语句创建并抛出异常,转换异常时传入原始异常以保留完整栈(Caused by)。
finally规则 无论是否抛出异常,finally都会执行;避免在finally中抛异常,防止屏蔽原始异常。
异常屏蔽 通过addSuppressed()保存被屏蔽异常,通常无需处理,但需知晓机制。
调试建议 贴出完整异常栈(含Caused by),是定位问题的关键线索。

自定义异常

一、Java 标准库异常体系
  1. 核心结构

    • 顶层异常为Exception,分为RuntimeException(运行时异常)和受检异常(如IOException)。
    • 常见运行时异常:NullPointerExceptionIllegalArgumentExceptionIndexOutOfBoundsException等。
    • 常见受检异常:IOExceptionSQLExceptionParseException等。
二、自定义异常的设计原则
  1. 优先复用标准异常

    • 当参数检查不合法时,应抛出IllegalArgumentException及其子类(如NumberFormatException)。
    • 避免重复造轮子,标准异常已覆盖大多数通用场景。
  2. 定义根异常(BaseException)

    • 继承选择 :推荐从RuntimeException派生,作为业务异常的根类,避免强制try-catch
    • 作用:统一业务异常体系,便于集中处理和区分系统级异常(如 JDK 原生异常)。
  3. 派生业务异常

    • BaseException派生出具体业务异常类,如UserNotFoundException(用户未找到)、LoginFailedException(登录失败)。
    • 示例代码:
    java 复制代码
    public class BaseException extends RuntimeException {
        // 构造方法需覆盖父类所有重载形式
        public BaseException() { super(); }
        public BaseException(String message) { super(message); }
        public BaseException(String message, Throwable cause) { super(message, cause); }
        public BaseException(Throwable cause) { super(cause); }
    }
    
    public class UserNotFoundException extends BaseException { /* 同上构造方法 */ }
    public class LoginFailedException extends BaseException { /* 同上构造方法 */ }
三、构造方法的实现要求
  • 完整覆盖父类构造:需包含无参、单字符串参数、字符串 + Throwable、单 Throwable 参数四种构造方法,确保异常创建的灵活性。
  • IDE 辅助:通过 IDE 自动生成构造方法,避免手动编写错误。

四、最佳实践

  1. 异常分类清晰

    • 根异常BaseException作为业务异常顶层,便于全局捕获(如catch (BaseException e)统一处理业务逻辑异常)。
  2. 减少受检异常

    • 继承RuntimeException的非受检异常,无需在方法签名中声明throws,简化代码结构。
  3. 提供有意义的错误信息

    • 构造异常时传入具体消息(如new UserNotFoundException("用户ID 123未找到")),便于调试定位。

Java断言

一、核心概念
  • 断言定义 :一种调试工具,用于在开发和测试阶段验证程序状态,通过 assert 关键字实现。
  • 作用:确保代码逻辑符合预期,捕获潜在错误,辅助调试。
二、语法与用法
  1. 基本语法

    java

    java 复制代码
    assert 条件表达式; // 断言条件为 true,否则抛出 AssertionError
    assert 条件表达式 : 断言消息; // 失败时附带自定义消息
    • 例:assert x >= 0 : "x 必须非负";
  2. 执行逻辑

    • 条件为 false 时,抛出 AssertionError(Error 的子类),终止程序。
    • 断言消息可选,用于增强错误信息可读性。
三、核心特点
  1. 适用场景

    • 仅限开发 / 测试阶段:用于检查 "不应该发生" 的错误(如参数合法性的极端假设)。
    • 不处理可恢复错误 :对可预期的程序错误(如 null 参数),应显式抛出异常(如 IllegalArgumentException)并由上层处理,而非断言。
  2. 默认行为

    • JVM 默认关闭断言assert 语句会被忽略,需手动启用。
四、启用断言的方式
  1. 全局启用

    bash 复制代码
    java -enableassertions 或 -ea 类名
    • 例:java -ea Main
  2. 局部启用

    • 指定类-ea:包名.类名(如 -ea:com.example.Main)。
    • 指定包(含子包)-ea:包名...(如 -ea:com.example...)。

反射

反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息。所以,反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。

Class 类

一、Class 类本质
  1. 定义 :JVM 为每个加载的class(含interface)创建的唯一实例,存储类的完整元信息(类名、包名、父类、接口、字段、方法等)。

  2. 特性

    • 由 JVM 动态加载,首次使用时加载到内存。
    • Class类构造方法为private,仅 JVM 可创建实例,用户无法直接实例化。
    • 每个class/interface对应唯一Class实例,可通过==比较是否为同一类型。
二、获取 Class 实例的三种方式
方法 示例 说明
类字面量(.class Class<String> cls1 = String.class; 直接通过类的静态变量class获取,编译期确定类型
实例的getClass() String str = "hello"; <br> Class<?> cls2 = str.getClass(); 通过对象实例调用,返回其运行时类型的Class实例
Class.forName("全类名") Class<?> cls3 = Class.forName("java.lang.String"); 运行期根据全类名动态加载,可能抛出ClassNotFoundException
  • 关键点 :三种方式获取的Class实例相同,可通过==验证(如cls1 == cls2 == cls3true)。
三、Class 实例核心功能
1. 类型信息查询
方法 作用 示例
getName() 返回完整类名(含包名) String[].class.getName()[Ljava.lang.String;(数组类名特殊格式)
getSimpleName() 返回简单类名(无包名) String.class.getSimpleName()String
getPackage() 获取包信息 String.class.getPackage().getName()java.lang
isInterface() 判断是否为接口 Runnable.class.isInterface()true
isEnum() 判断是否为枚举类 Month.class.isEnum()true
isArray() 判断是否为数组类型 String[].class.isArray()true
isPrimitive() 判断是否为基本类型 int.class.isPrimitive()true
2. 创建类实例
  • 方法newInstance()

  • 示例

    java 复制代码
    Class<String> cls = String.class; 
    String obj = cls.newInstance(); // 等价于 `new String()`(调用无参构造方法)
  • 限制 :仅能调用public无参构造方法,不支持有参或非public构造方法。

3. 与instanceof的区别
特性 instanceof Class实例比较(==
核心功能 判断对象是否为某类型或其子类型(支持多态) 精确判断是否为同一类型(不考虑继承关系)
示例 Integer num instanceof Numbertrue(多态匹配父类) num.getClass() == Number.classfalseIntegerNumber子类,类型不同)
四、动态加载机制
  1. JVM 特性:按需加载,首次使用时加载类到内存(如首次调用类的方法、创建实例或访问静态成员时)。

  2. 应用场景:运行期根据条件加载不同实现类(如日志框架优先级选择)

    java 复制代码
    // 示例:优先使用Log4j,不存在则用JDK日志
    boolean isLog4jPresent = false; 
    try { 
        Class.forName("org.apache.log4j.Logger"); 
        isLog4jPresent = true; // 加载Log4j类,存在则使用 
    } catch (ClassNotFoundException e) { 
        // 未找到Log4j,使用JDK日志 
    }
五、特殊类型的 Class 实例
  1. 基本类型 :JVM 预定义Class实例,如int.classboolean.classvoid.class

  2. 数组类型

    • 引用类型数组:类名为[L全类名;(如String[].class[Ljava.lang.String;)。
    • 基本类型数组:类名为[基本类型缩写(如int[][Iboolean[][Z)。

Java 反射之访问字段

一、核心知识点:通过反射获取字段信息
  1. 获取Field实例的方法

    • getField(name):获取某个public 字段(包括父类)。
    • getDeclaredField(name):获取当前类的任意字段(不包括父类,可获取 private/protected/default 字段)。
    • getFields():获取所有public 字段(包括父类)。
    • getDeclaredFields():获取当前类的所有字段(不包括父类)。
  2. Field对象包含的字段信息

    • getName():返回字段名称(如"name")。
    • getType():返回字段类型(Class实例,如String.class,数组类型用[B表示byte[])。
    • getModifiers():返回字段修饰符(int值),可通过Modifier类判断(如Modifier.isPrivate(m))。
二、核心操作:读写字段值
  1. 获取字段值(Field.get(Object instance)

    • 示例:Object value = field.get(p),获取实例p的字段值。
    • 访问限制 :若字段为非 public(如 private),需先调用field.setAccessible(true)突破访问限制(可能受SecurityManager限制,如无法修改 Java 核心类字段)。
  2. 设置字段值(Field.set(Object instance, Object value)

    • 示例:field.set(p, "新值"),修改实例p的字段值。
    • 注意 :同样需通过setAccessible(true)访问非 public 字段,支持修改基本类型和引用类型字段。
三、代码示例与注意事项
  1. 示例代码逻辑

    • 获取字段 :通过Class实例(如Student.classp.getClass())调用字段获取方法。
    • 突破封装:反射可访问私有字段,但会破坏类的封装性,仅用于工具 / 框架等非常规场景。
  2. 安全限制

    • setAccessible(true)可能被SecurityManager阻止(如 Java 核心包java.*/javax.*的类),确保 JVM 安全。
四、小结
  1. 反射访问字段的核心步骤

    • 通过Class获取Field实例(区分是否包含父类、是否 public)。
    • 通过Field获取字段元信息(名称、类型、修饰符)。
    • 通过get()/set()读写字段值,非 public 字段需setAccessible(true)
  2. 适用场景与风险

    • 用途:底层框架、工具开发(未知对象结构时动态操作字段)。
    • 风险:破坏封装性,代码繁琐,受安全策略限制,需谨慎使用。

调用方法

二、核心内容总结
1. 获取 Method 对象的方法

通过Class实例获取Method对象,支持不同范围和权限的方法获取:

  • getMethod(String name, Class... parameterTypes) :获取类及其父类的public 方法(需指定参数类型)。
  • getDeclaredMethod(String name, Class... parameterTypes) :获取当前类的任意方法(包括 private,不包括父类)。
  • getMethods() :获取类及其父类的所有 public 方法 (返回Method[])。
  • getDeclaredMethods() :获取当前类的所有方法 (包括 private,返回Method[])。
2. Method 对象的信息获取

通过Method实例可获取方法的元数据:

  • getName() :返回方法名称(如"getScore")。
  • getReturnType() :返回返回值类型(Class实例,如String.class)。
  • getParameterTypes() :返回参数类型数组(Class[],如{String.class, int.class})。
  • getModifiers() :返回修饰符(int类型,通过位运算解析 public/private 等)。
3. 调用方法的方式
  • 实例方法调用 :使用Object invoke(Object instance, Object... args)instance为方法所属对象,args为参数。
    示例

    java 复制代码
    Method m = String.class.getMethod("substring", int.class);
    String result = (String) m.invoke("Hello", 2); // 调用"ello"
  • 静态方法调用invoke的第一个参数为null(无需实例)。
    示例

    java 复制代码
    Method m = Integer.class.getMethod("parseInt", String.class);
    Integer num = (Integer) m.invoke(null, "123"); // 调用静态方法
  • 非 public 方法调用 :需先通过setAccessible(true)破除访问限制(可能受SecurityManager限制)。
    示例

    java 复制代码
    Method privateMethod = MyClass.class.getDeclaredMethod("privateMethod");
    privateMethod.setAccessible(true);
    privateMethod.invoke(instance);
4. 多态特性

反射调用方法时遵循多态原则:调用实际对象类型的覆写方法(即使通过父类Class获取方法)。
示例

子类Student覆写父类Personhello()方法,通过Person.class.getMethod("hello")获取方法后,调用Student实例仍执行子类实现。

三、关键代码示例
  1. 获取不同权限的方法

    java 复制代码
    Class<Student> clazz = Student.class;
    clazz.getMethod("publicMethod"); // 公共方法(含父类)
    clazz.getDeclaredMethod("privateMethod"); // 当前类私有方法
  2. 处理方法重载 :通过参数类型区分同名方法,如获取substring(int start)substring(int start, int end)需指定不同参数类型。

  3. 异常处理 :调用invoke可能抛出IllegalAccessException(权限问题)、InvocationTargetException(方法内部异常)等,需显式处理或声明抛出。

四、小结
  • 反射调用方法的核心步骤 :获取Class→获取Method→调用invoke(处理权限和参数)。
  • 方法访问范围getMethod用于公共方法(含继承),getDeclaredMethod用于当前类任意方法(需破除权限)。
  • 多态支持:反射调用严格遵循 Java 多态规则,实际调用对象的覆写方法。
  • 应用场景:动态调用未知方法、框架底层扩展(如 Spring 依赖注入)、单元测试工具等。

调用构造方法(Java 反射机制)

一、实例创建的常规方式与反射基础
  1. 常规实例创建

    使用 new 操作符直接创建实例:

    java 复制代码
    Person p = new Person();
  2. 反射创建实例(无参构造)

    通过 Class.newInstance() 方法调用 public 无参数构造方法

    java 复制代码
    Person p = Person.class.newInstance();

    局限:仅支持无参且 public 的构造方法,无法处理有参或非 public 构造方法。

二、Constructor 对象:反射调用任意构造方法

Constructor 类封装了构造方法的详细信息,可用于创建实例,支持 有参、非 public 构造方法

  1. 获取 Constructor 对象的方法

    • getConstructor(Class<?>... parameterTypes):获取 public 构造方法(指定参数类型)。
    • getDeclaredConstructor(Class<?>... parameterTypes):获取 任意访问权限 的构造方法(包括非 public)。
    • getConstructors():获取所有 public 构造方法(返回数组)。
    • getDeclaredConstructors():获取所有构造方法(包括非 public,返回数组)。

    注意:构造方法属于当前类,与父类无关,无多态特性。

  2. 调用构造方法创建实例

    使用 newInstance(Object... args) 传入参数创建实例:

    java 复制代码
    // 调用 Integer(int) 构造方法
    Constructor<Integer> cons1 = Integer.class.getConstructor(int.class);
    Integer n1 = cons1.newInstance(123); // 结果:123
    
    // 调用 Integer(String) 构造方法
    Constructor<Integer> cons2 = Integer.class.getConstructor(String.class);
    Integer n2 = cons2.newInstance("456"); // 结果:456
  3. 访问非 public 构造方法

    需通过 setAccessible(true) 强制设置访问权限(可能受安全策略限制):

    java 复制代码
    Constructor<MyClass> cons = MyClass.class.getDeclaredConstructor();
    cons.setAccessible(true); // 允许访问非 public 构造方法
    MyClass obj = cons.newInstance();
三、核心要点总结
  1. Constructor 的作用

    封装构造方法信息,支持调用 任意访问权限、任意参数的构造方法 ,弥补 Class.newInstance() 的局限。

  2. 关键方法对比

    方法名 功能描述 访问权限 参数类型匹配
    getConstructor 获取 public 构造方法 public 精确匹配参数
    getDeclaredConstructor 获取任意构造方法(包括非 public) 任意 精确匹配参数
    getConstructors 获取所有 public 构造方法(数组) public 全部
    getDeclaredConstructors 获取所有构造方法(包括非 public) 任意 全部
  3. 注意事项

    • 非 public 构造方法需通过 setAccessible(true) 解锁访问,可能抛出安全异常。
    • 构造方法调用结果始终返回实例,无返回值(与普通方法不同)。
相关推荐
ademen12 分钟前
spring4第6课-bean之间的关系+bean的作用范围
java·spring
cccl.12 分钟前
Java在word中指定位置插入图片。
java·word
kingbal13 分钟前
Elasticsearch:spring2.x集成elasticsearch8.x
java·spring2.x·elastic8.x
三两肉2 小时前
Java 中 ArrayList、Vector、LinkedList 的核心区别与应用场景
java·开发语言·list·集合
yuren_xia2 小时前
Spring Boot中保存前端上传的图片
前端·spring boot·后端
clk66074 小时前
SSM 框架核心知识详解(Spring + SpringMVC + MyBatis)
java·spring·mybatis
JohnYan5 小时前
Bun技术评估 - 04 HTTP Client
javascript·后端·bun
shangjg35 小时前
Kafka 的 ISR 机制深度解析:保障数据可靠性的核心防线
java·后端·kafka
青莳吖6 小时前
使用 SseEmitter 实现 Spring Boot 后端的流式传输和前端的数据接收
前端·spring boot·后端
我的golang之路果然有问题7 小时前
ElasticSearch+Gin+Gorm简单示例
大数据·开发语言·后端·elasticsearch·搜索引擎·golang·gin