Java泛型与反射核心进阶|泛型类/方法/接口、类型擦除、通配符、Class类、动态调用

泛型负责把类型安全前移到编译期,反射则把对象操作延后到运行期;两者一起构成了 Java 框架设计里最重要的一组基础能力。

泛型与反射是Java高级特性的核心基石,也是框架底层(Spring、MyBatis)的核心原理。泛型解决代码类型安全、复用性问题,编译期约束数据类型;反射突破封装限制,在运行时动态获取类信息、调用方法、操作属性。

本文全覆盖核心考点:泛型类/泛型方法/泛型接口、通配符、类型擦除、Class类、Field字段、Constructor构造器、Method方法动态调用,搭配完整实战代码、底层原理、高频坑点、工程规范、面试标准答案,零基础可学、面试可背、框架源码可落地。

一、泛型核心概述

1.1 什么是泛型

泛型(Generic):JDK5推出的语法特性,编译期类型约束,允许在类、方法、接口上定义未知类型,使用时再指定具体类型。

1.2 泛型核心作用

  • 类型安全:编译期校验数据类型,杜绝集合存储任意类型导致的类型转换异常

  • 避免强转:无需手动强制类型转换,代码更简洁优雅

  • 代码复用:一套代码适配多种数据类型,不用为不同类型重复定义类和方法

1.3 泛型常用标识符(规范约定)

  • T:Type(代表任意类型)

  • E:Element(集合元素类型)

  • K/V:Key/Value(键值对类型,适配Map)

  • N:Number(数值类型)

二、泛型三大基础:类、方法、接口

2.1 泛型类

在类定义时声明泛型类型,整个类内部均可使用该泛型,创建对象时指定具体类型。

2.1.1 实战代码

// 定义泛型类,T为未知通用类型
public class GenericClass<T> {
    // 泛型成员变量
    private T data;

    // 泛型方法
    public T getData() {
        return data;
    }

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

    // 测试
    public static void main(String[] args) {
        // 指定泛型为String类型
        GenericClass<String> strObj = new GenericClass<>();
        strObj.setData("Java泛型");
        System.out.println(strObj.getData());

        // 指定泛型为Integer类型
        GenericClass<Integer> intObj = new GenericClass<>();
        intObj.setData(666);
        System.out.println(intObj.getData());
    }
}

特点:对象创建时确定具体类型,全局通用,不指定类型则默认Object类型。

2.2 泛型方法

方法级别定义泛型,独立于类泛型,调用方法时自动匹配具体类型,灵活性高于泛型类。

2.2.1 实战代码

public class GenericMethod {
    // 定义泛型方法:修饰符 <泛型标识> 返回值 方法名
    public static <T> T printData(T data) {
        System.out.println("泛型方法接收数据:" + data);
        return data;
    }

    // 多泛型参数方法
    public static <K,V> void printKeyValue(K key, V value) {
        System.out.println("key:" + key + ",value:" + value);
    }

    public static void main(String[] args) {
        // 自动匹配类型
        printData("字符串类型");
        printData(123456);
        printKeyValue("姓名", "张三");
        printKeyValue("年龄", 20);
    }
}

核心规则:泛型方法必须在返回值前声明\&lt;T\&gt;,静态方法只能使用自身定义的泛型,无法使用类泛型。

2.3 泛型接口

接口定义泛型,实现类可指定具体类型,或延续泛型定义,常用于统一接口规范、通用工具封装。

2.3.1 实战代码

// 定义泛型接口
public interface GenericInterface<T> {
    // 抽象方法使用泛型
    T getResult(T param);
}

// 方式1:实现类指定具体类型
class StringInterfaceImpl implements GenericInterface<String> {
    @Override
    public String getResult(String param) {
        return "处理结果:" + param;
    }
}

// 方式2:实现类延续泛型,创建对象时再确定类型
class GenericInterfaceImpl<T> implements GenericInterface<T> {
    @Override
    public T getResult(T param) {
        return param;
    }
}

// 测试
class Test {
    public static void main(String[] args) {
        StringInterfaceImpl strImpl = new StringInterfaceImpl();
        System.out.println(strImpl.getResult("测试泛型接口"));

        GenericInterfaceImpl<Integer> intImpl = new GenericInterfaceImpl<>();
        System.out.println(intImpl.getResult(999));
    }
}

三、泛型通配符(高频面试)

通配符用于不确定具体泛型类型时使用,解决泛型参数多类型适配问题,核心分为三种:无界通配符、上界通配符、下界通配符。

3.1 无界通配符 ?

代表任意未知类型,可接收任意泛型类型的集合,只能读取、不能写入(除null)。

import java.util.ArrayList;
import java.util.List;

public class WildcardDemo {
    // 接收任意泛型的List集合
    public static void printList(List<?> list) {
        for (Object o : list) {
            System.out.println(o);
        }
    }

    public static void main(String[] args) {
        List<String> strList = List.of("Java","Python");
        List<Integer> intList = List.of(1,2,3);
        printList(strList);
        printList(intList);
    }
}

3.2 上界通配符 ?extends 父类

规则:只能接收 指定类及其子类,上限受限,只能读、不能写

适用场景:只读取数据,不修改集合内容。

import java.util.List;

public class ExtendsDemo {
    // 只能接收Number及其子类(Integer、Double、Long)
    public static void showNum(List<? extends Number> list) {
        for (Number number : list) {
            System.out.println(number);
        }
    }

    public static void main(String[] args) {
        showNum(List.of(1,2,3));
        showNum(List.of(1.1,2.2));
        // 报错:String不是Number子类
        // showNum(List.of("123"));
    }
}

3.3 下界通配符 ?super 子类

规则:只能接收 指定类及其父类,下限受限,可读可写

适用场景:需要写入、修改集合数据。

import java.util.List;
import java.util.ArrayList;

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

    public static void main(String[] args) {
        List<Number> numList = new ArrayList<>();
        addNum(numList);
        System.out.println(numList);
    }
}

3.4 通配符面试总结(必背)

  • ? extends T:上界通配符,取子类,只读不写

  • ? super T:下界通配符,取父类,可读可写

  • ?:无界通配符,任意类型,只读不写

四、类型擦除(泛型底层核心)

4.1 什么是类型擦除

Java泛型是伪泛型,仅在编译期生效,编译成功后会擦除所有泛型信息,最终运行的都是原始Object类型,目的是兼容JDK5之前的无泛型代码。

4.2 擦除规则

  • 无边界泛型&lt;T&gt;:擦除为 Object

  • 有上界泛型&lt;T extends Number&gt;:擦除为 上界类型Number

  • 泛型方法、泛型类、集合泛型全部擦除

4.3 擦除验证代码

import java.util.ArrayList;

public class EraseDemo {
    public static void main(String[] args) {
        ArrayList<String> strList = new ArrayList<>();
        ArrayList<Integer> intList = new ArrayList<>();

        // 类型擦除后,两者Class对象完全一致
        System.out.println(strList.getClass() == intList.getClass()); // true
    }
}

4.4 类型擦除核心坑点

  • 运行期无法获取泛型真实类型

  • 泛型不能用于静态变量、静态方法(静态优先于对象加载,无泛型类型)

  • 无法创建泛型数组、无法new T()对象

  • 重载方法泛型擦除后会冲突

五、反射机制核心原理

5.1 反射定义

反射(Reflection):Java在运行状态中,动态获取任意类的完整结构(类名、属性、方法、构造器),并动态调用方法、操作属性的机制。

核心价值:突破封装限制,实现类的动态操作,Spring IoC、注解、框架动态代理全部基于反射实现。

5.2 反射核心四大核心类

  • Class:代表类的字节码对象,获取类的所有信息

  • Field:代表类的成员字段(属性),动态获取/修改属性值

  • Constructor:代表类的构造方法,动态创建对象

  • Method:代表类的成员方法,动态调用方法

六、Class 类(反射入口)

Class是所有反射操作的唯一入口,每个类加载后只会生成唯一的Class对象,全局共享。

6.1 获取Class对象的三种方式

public class GetClassDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        // 方式1:对象.getClass()
        User user = new User();
        Class<?> clazz1 = user.getClass();

        // 方式2:类名.class(最常用、高效)
        Class<User> clazz2 = User.class;

        // 方式3:Class.forName("全类名")(多用于配置文件反射创建对象)
        Class<?> clazz3 = Class.forName("com.entity.User");

        // 三个对象完全一致
        System.out.println(clazz1 == clazz2);
        System.out.println(clazz2 == clazz3);
    }
}

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

七、反射动态操作:构造器、字段、方法

基于Class对象,实现无视权限修饰符的动态创建对象、修改私有属性、调用私有方法,是反射核心实战能力。

7.1 Constructor 构造器反射(动态创建对象)

通过反射获取无参/有参构造方法,动态实例化对象,支持突破私有构造器限制。

import java.lang.reflect.Constructor;

public class ConstructorReflectDemo {
    public static void main(String[] args) throws Exception {
        // 1. 获取Class对象
        Class<User> clazz = User.class;

        // 2. 获取无参构造,创建对象
        Constructor<User> nullCon = clazz.getDeclaredConstructor();
        User user1 = nullCon.newInstance();
        System.out.println("无参构造创建:" + user1);

        // 3. 获取有参构造,创建对象
        Constructor<User> con = clazz.getDeclaredConstructor(String.class, int.class);
        User user2 = con.newInstance("张三", 20);
        System.out.println("有参构造创建:" + user2);
    }
}

class User {
    private String name;
    private int age;

    public User() {}

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + "}";
    }
}

7.2 Field 字段反射(动态读写属性)

动态获取、修改对象属性,支持操作private私有属性,需开启权限访问。

import java.lang.reflect.Field;

public class FieldReflectDemo {
    public static void main(String[] args) throws Exception {
        Class<User> clazz = User.class;
        User user = clazz.newInstance();

        // 获取私有name字段
        Field nameField = clazz.getDeclaredField("name");
        // 突破权限校验(核心!否则无法访问私有属性)
        nameField.setAccessible(true);
        // 动态赋值
        nameField.set(user, "李四");
        // 动态取值
        String name = (String) nameField.get(user);
        System.out.println("反射获取私有属性:" + name);

        // 获取公共age字段
        Field ageField = clazz.getField("age");
        ageField.set(user, 22);
        System.out.println(user);
    }
}

核心方法setAccessible\(true\) 暴力反射,解除权限修饰符限制。

7.3 Method 方法反射(动态调用方法)

动态获取类的普通方法、私有方法,传入参数执行调用,框架底层核心用法。

import java.lang.reflect.Method;

public class MethodReflectDemo {
    public static void main(String[] args) throws Exception {
        Class<User> clazz = User.class;
        User user = clazz.newInstance();

        // 1. 获取公共无参方法
        Method showMethod = clazz.getDeclaredMethod("show");
        // 动态调用方法
        showMethod.invoke(user);

        // 2. 获取私有有参方法
        Method printMethod = clazz.getDeclaredMethod("printInfo", String.class);
        printMethod.setAccessible(true);
        // 传参调用
        printMethod.invoke(user, "反射调用私有方法");
    }
}

class User {
    public void show() {
        System.out.println("公共普通方法执行");
    }

    private void printInfo(String msg) {
        System.out.println("私有方法输出:" + msg);
    }
}

invoke(Object obj, 参数):反射执行方法,第一个参数为对象实例,静态方法可传null。

八、泛型+反射综合实战(工程常用)

结合泛型封装通用反射工具类,实现任意对象属性赋值、方法调用,适配所有实体类,可直接用于项目。

import java.lang.reflect.Field;

// 通用泛型反射工具类
public class ReflectUtil {

    // 泛型通用属性赋值工具
    public static <T> void setFieldValue(T obj, String fieldName, Object value) throws Exception {
        Class<?> clazz = obj.getClass();
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    // 泛型通用属性取值工具
    public static <T> Object getFieldValue(T obj, String fieldName) throws Exception {
        Class<?> clazz = obj.getClass();
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        return field.get(obj);
    }

    // 测试
    public static void main(String[] args) throws Exception {
        User user = new User();
        // 通用反射赋值
        setFieldValue(user, "name", "王五");
        setFieldValue(user, "age", 25);
        // 通用反射取值
        System.out.println(getFieldValue(user, "name"));
        System.out.println(getFieldValue(user, "age"));
    }
}

九、泛型与反射工程坑点 &amp; 最佳实践

  • 泛型无基本类型:泛型只能用包装类、引用类型,不支持int、byte等基本类型

  • 类型擦除导致运行无泛型:运行期无法通过泛型获取真实类型,需通过反射获取泛型参数化类型

  • 反射性能较低:反射绕过编译优化,频繁反射需缓存Class、Method、Field对象

  • 暴力反射慎用:setAccessible(true)破坏封装性,滥用会导致代码维护性变差

  • 通配符场景规范:查询用extends、修改用super,杜绝乱用?通配符

  • 静态资源不支持泛型:静态方法、静态变量无法使用类泛型

十、全文核心总结(面试必背)

  • 泛型核心:编译期类型约束,实现类型安全、代码复用,运行期类型擦除

  • 泛型结构:泛型类全局生效、泛型方法独立灵活、泛型接口统一规范

  • 通配符:extends上界只读、super下界可写、?任意类型

  • 类型擦除:编译后擦除泛型,是Java伪泛型的核心原因,兼容旧版本

  • 反射入口:Class对象是反射唯一入口,三种获取方式

  • 反射三大操作:Constructor动态创建对象、Field动态操作属性、Method动态调用方法

  • 反射核心能力:突破权限限制,运行期动态操作类所有资源,是框架底层核心

十一、高频面试简答题

  • 什么是类型擦除? 泛型仅编译期生效,编译完成后擦除泛型信息,还原为原始类型,实现版本兼容。

  • extends和super通配符区别? extends限定子类、只读;super限定父类、可读写,适配不同业务场景。

  • 泛型为什么不支持静态? 静态资源属于类,泛型属于对象,类加载优先于对象,无法确定泛型类型。

  • 反射的优缺点? 优点:动态灵活、突破封装、框架底层核心;缺点:性能低、破坏封装、安全性差。

  • newInstance()和new对象区别? new是编译期创建对象,反射是运行期动态创建,无需提前确定类类型。

  • 暴力反射是什么? 通过setAccessible(true)解除权限修饰符限制,可访问私有属性和方法。

  • Java泛型是真泛型吗? 不是,属于伪泛型,基于类型擦除实现,运行期无泛型信息。

(注:文档部分内容可能由 AI 生成)

本文总结

  • 泛型解决的是类型安全与复用,反射解决的是运行期动态访问与调用,它们在框架设计里经常成对出现。
  • 通配符和类型擦除是泛型题里最容易答虚的部分,必须把编译期约束和运行期消失这两个阶段拆开理解。
  • Class、Field、Constructor、Method 构成了反射操作的四个核心入口,动态创建对象和方法调用都建立在这套模型之上。
GYSTACK 文章文末广告 硅云云服务器活动 适合个人项目、轻量建站和出海业务部署。
后浪云移动端信息流广告 后浪云主机服务 适合长期部署、独立站和海外机房需求。