1.反射

Java 反射(Reflection)是运行时动态获取类信息、创建对象、调用方法 / 访问字段的机制,核心入口是java.lang.Class,操作 API 在java.lang.reflect包。

每个类都有一个 Class 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。

1.1核心概念

  • 定义:反射让程序在运行时 “自省”—— 查看类的结构(构造器、方法、字段、注解)、动态造对象、调用任意方法 / 读写任意字段(含private)。
  • 本质:类加载后,JVM 在方法区生成唯一Class对象,反射通过该对象逆向操作类的元数据。
  • 核心类
    • Class<?>:类元数据入口
    • Constructor:构造器
    • Method:方法
    • Field:字段
    • Modifier:修饰符工具

1.2获取 Class 对象(3 种)

// 1. 类名.class(编译期安全,常用)
Class<String> cls1 = String.class;

// 2. 对象.getClass()(实例获取)
String str = "hello";
Class<? extends String> cls2 = str.getClass();

// 3. Class.forName("全限定名")(运行时动态加载,框架常用)
Class<?> cls3 = Class.forName("java.lang.String");

1.3常用反射操作

1.创建对象实例

class User {
    public User() {}
    public User(String name) {}
    private User(int age) {}
}

// 无参构造
Class<User> userClass = User.class;
User u1 = userClass.newInstance(); // 过时,推荐下面

// 显式无参构造
User u2 = userClass.getDeclaredConstructor().newInstance();

// 有参构造
User u3 = userClass.getConstructor(String.class).newInstance("Tom");

// 私有构造(需开访问权限)
Constructor<User> privateCtor = userClass.getDeclaredConstructor(int.class);
privateCtor.setAccessible(true); // 突破private
User u4 = privateCtor.newInstance(18);

2.访问 / 修改字段(Field)

class User {
    public String name;
    private int age;
}

User user = new User();
Class<User> cls = User.class;

// 公有字段
Field nameField = cls.getField("name");
nameField.set(user, "Alice"); // 写
System.out.println(nameField.get(user)); // 读:Alice

// 私有字段
Field ageField = cls.getDeclaredField("age");
ageField.setAccessible(true); // 必加
ageField.set(user, 20);
System.out.println(ageField.get(user)); // 20

3.调用方法(Method)

class User {
    public void sayHello() { ... }
    private String getMsg(String s) { return s; }
}

User user = new User();
Class<User> cls = User.class;

// 公有无参方法
Method sayHello = cls.getMethod("sayHello");
sayHello.invoke(user); // 调用

// 私有有参方法
Method getMsg = cls.getDeclaredMethod("getMsg", String.class);
getMsg.setAccessible(true);
String res = (String) getMsg.invoke(user, "Hi");
System.out.println(res); // Hi

4. 获取类的完整信息

Class<?> cls = User.class;

// 类名
System.out.println(cls.getName()); // 全限定名
System.out.println(cls.getSimpleName()); // 简单名

// 构造器
Constructor<?>[] ctors = cls.getDeclaredConstructors();

// 方法(含私有)
Method[] methods = cls.getDeclaredMethods();

// 字段(含私有)
Field[] fields = cls.getDeclaredFields();

// 父类/接口
Class<?> superCls = cls.getSuperclass();
Class<?>[] interfaces = cls.getInterfaces();

1.4 setAccessible (true) 关键作用

  • 作用:关闭 Java 语言访问检查,突破private/protected/ 包私有限制,访问私有成员。
  • 风险:破坏封装、安全管理器可拦截、模块系统(Java 9+)会限制。

1.5 优缺点

优点

  • 动态性:运行时确定类型,无需编译期硬编码。
  • 框架基石:Spring IoC、AOP、MyBatis、JUnit 等底层依赖反射。
  • 通用工具:序列化、JSON 解析、动态代理等。

缺点

  • 性能差:反射绕开编译期优化,比直接调用慢 5~10 倍。
  • 安全风险:可访问私有成员,破坏封装。
  • 维护难:字符串硬编码(如方法名),编译期不报错,运行时才发现。
  • 兼容性:Java 9 + 模块系统对反射有严格限制。

1.6 总结

反射是 Java 动态性的核心,运行时可操作类的所有成员,是框架和工具的底层必备技术。但因性能、安全、维护问题,业务代码应尽量少用,优先用接口 / 工厂模式;框架层合理使用,配合setAccessible(true)突破访问限制

2.异常

Java 异常是程序运行时出现的错误或意外情况(如空指针、数组越界、文件找不到),Java 用面向对象的方式统一处理异常,让程序更健壮、易维护。

2.1 异常体系结构(核心)

所有异常的根父类java.lang.Throwable

它有两个直接子类:

1. Error(错误)

  • JVM 系统级错误,程序无法处理
  • 例如:OOM 内存溢出、栈溢出、虚拟机崩溃
  • 我们不需要捕获,也捕获不住

2. Exception(异常)

  • 程序级错误,可以捕获、处理、修复
  • 分两大类:
    1. 运行时异常(RuntimeException)
    2. 编译时异常(非 RuntimeException)

2.2 两大异常分类

1. 运行时异常(RuntimeException)

  • 不强制捕获,编译不报错,运行时才抛出
  • 大多是代码逻辑错误导致
  • 最常见的:
    • NullPointerException 空指针
    • IndexOutOfBoundsException 数组越界
    • IllegalArgumentException 参数不合法
    • ClassCastException 类型转换异常

2. 编译时异常(Checked Exception)

  • 必须捕获或声明抛出,否则编译不通过
  • 不是代码逻辑问题,是外部环境问题
  • 常见:
    • IOException 文件 / IO 异常
    • SQLException 数据库异常
    • ClassNotFoundException 类找不到

2.3 异常处理 5 大关键字

try   监控区域
catch 捕获异常
finally 最终必须执行(常用于关流、关连接)
throw 手动抛出一个异常
throws 方法上声明可能抛出的异常

2.4 最标准的异常处理写法

1. 基础捕获

try {
    // 可能出异常的代码
    int res = 10 / 0;
} catch (ArithmeticException e) {
    // 异常处理
    System.out.println("除数不能为0");
    e.printStackTrace(); // 打印异常堆栈(调试用)
} finally {
    // 无论是否异常,都会执行
    System.out.println("finally 一定执行");
}

2.多异常捕获

try {
    // ...
} catch (NullPointerException e) {
    // 处理空指针
} catch (IndexOutOfBoundsException e) {
    // 处理越界
} catch (Exception e) {
    // 捕获其他所有异常(兜底)
}

3.手动抛异常(throw)

public void checkAge(int age) {
    if (age < 0) {
        // 手动抛出运行时异常
        throw new IllegalArgumentException("年龄不能为负数");
    }
}

4.方法声明异常(throws)

// 告诉调用者:我这里可能抛出IO异常
public void readFile() throws IOException {
    // 读文件代码
}

2.5 总结

  • Throwable:异常顶层父类
  • Error:JVM 错误,管不了
  • Exception:程序异常,能处理
    • RuntimeException:运行时异常,不强制捕获
    • CheckedException:编译时异常,必须捕获
  • try-catch-finally:捕获处理
  • throw/throws:抛出异常
  • 自定义异常:继承 RuntimeException 最常用

3.泛型

Java 泛型是 JDK 5 引入的特性,核心作用是:在编译时检查类型安全,避免运行时类型转换异常,同时让代码更通用、可复用

简单说:泛型 = 参数化类型,把类型当作参数传递,让类 / 方法 / 接口可以适配多种数据类型

3.1 为什么需要泛型?

先看没有泛型的问题:

List list = new ArrayList();
list.add("Hello");
list.add(100); // 可以存任意类型,编译不报错

// 取出时必须强转,很容易出错
String str = (String) list.get(1); // 运行时报错:ClassCastException

问题:

  1. 编译期无法发现类型错误,运行时才崩溃
  2. 频繁强制类型转换,代码臃肿、易出错
  3. 集合能存任意对象,类型不安全

使用泛型后:

List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(100); 编译直接报错,提前发现问题
String str = list.get(0); // 无需强转

3.2 泛型的核心概念

1. 泛型标识(常用字母)

约定俗成的写法,方便阅读:

  • T:Type(任意类型)
  • E:Element(集合元素类型)
  • K:Key(键)
  • V:Value(值)
  • N:Number(数字类型)

2. 泛型的使用场景

  • 泛型类
  • 泛型接口
  • 泛型方法
  • 通配符

3.3 泛型的三种使用方式

1. 泛型类

把类型定义在类上,整个类都可以使用这个类型。

// 泛型类:T 是类型参数
public class Box<T> {
    private T data;

    public void set(T data) {
        this.data = data;
    }

    public T get() {
        return data;
    }
}

使用:

Box<String> box1 = new Box<>();
box1.set("字符串");
String s = box1.get();

Box<Integer> box2 = new Box<>();
box2.set(100);
int i = box2.get();

2. 泛型接口

和泛型类用法一致,定义在接口上。

public interface Generator<T> {
    T generate();
}

实现类:

// 实现时指定类型
public class StringGenerator implements Generator<String> {
    @Override
    public String generate() {
        return "生成字符串";
    }
}

3. 泛型方法

方法自己独立定义泛型,不依赖类的泛型。

public class Utils {
    // 泛型方法:<T> 声明泛型,T 是返回值/参数类型
    public static <T> T getFirstElement(T[] array) {
        if (array == null || array.length == 0) {
            return null;
        }
        return array[0];
    }
}

使用:

String[] strArr = {"a", "b"};
String first = Utils.getFirstElement(strArr);

Integer[] intArr = {1, 2, 3};
Integer firstInt = Utils.getFirstElement(intArr);

3.4 泛型通配符(?)

通配符 ? 表示任意类型,主要用于灵活接收泛型对象。

1. 无界通配符 ?

// 可以接收任意泛型类型
public static void printList(List<?> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
}

2. 上界通配符 ? extends 类型

只接收 该类型 及其 子类(读安全,不能写)

// 只能接收 Number 及其子类(Integer、Double等)
public static void printNumbers(List<? extends Number> list) { }

3. 下界通配符 ? super 类型

只接收 该类型 及其 父类(写安全)

// 只能接收 Integer 及其父类(Number、Object)
public static void addIntegers(List<? super Integer> list) {
    list.add(100);
}

3.5 泛型的重要规则

  • 泛型只在编译期有效,运行时会被类型擦除(编译后变成原始类型,如 List
  • 不能使用基本类型(必须用包装类 Integer/Double
  • 静态方法 / 变量不能使用类的泛型,只能用自己的泛型
  • 不能使用 instanceof 判断泛型类型
  • 不能创建泛型数组:new T[10] 错误

3.6 总结

  1. 泛型 = 参数化类型,让代码通用且类型安全
  2. 三大使用方式:泛型类、泛型接口、泛型方法
  3. 通配符:?(任意)、? extends(上限)、? super(下限)
  4. 核心价值:编译期检查 + 避免强转 + 代码复用

4.注解

Java 注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。

4.1 内置三大基础注解(常用)

1. @Override

重写校验,编译检查

  • 必须重写父类 / 接口方法
  • 方法名、参数、返回值错了直接编译报错
@Override
public String toString() {
    return "xxx";
}

2. @Deprecated

标记过时、废弃方法 / 类 / 字段

  • 不推荐使用,编译器标删除线警告

3. @SuppressWarnings

压制编译器警告

@SuppressWarnings("all")       // 忽略所有警告
@SuppressWarnings("unchecked")// 忽略泛型未检查强转警告

4.2 元注解(用来「修饰注解」的注解)

自定义注解必须掌握,共 5 个核心元注解:

1. @Target

指定注解能加在哪些位置

// 常用取值
ElementType.TYPE       // 类、接口、枚举
ElementType.FIELD      // 成员变量
ElementType.METHOD     // 方法
ElementType.PARAMETER  // 方法参数
ElementType.CONSTRUCTOR// 构造器
ElementType.LOCAL_VARIABLE // 局部变量

2. @Retention

指定注解保留时机(关键)

  • RetentionPolicy.SOURCE:仅源码阶段,编译后丢弃(如 @Override)
  • CLASS:保留到 class 文件,运行时丢失(默认)
  • RUNTIME:保留到运行时,可通过反射读取(框架核心)

3. @Documented

生成 javadoc 时,保留该注解文档

4. @Inherited

子类自动继承父类上的该注解

5. @Repeatable

JDK8+,同一位置可重复加多个相同注解

4.3 自定义注解

1. 定义格式

// 元注解 + @interface 关键字
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldDesc {
    // 注解属性:格式 类型 名() default 默认值;
    String value() default "";
    int sort() default 1;
}

注解属性支持类型:基本类型、String、枚举、注解、以上类型数组

2. 使用自定义注解

public class User {
    @FieldDesc(value = "用户名", sort = 2)
    private String username;
    
    @FieldDesc(value = "年龄")
    private Integer age;
}

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐