泛型与反射是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);
}
}
核心规则:泛型方法必须在返回值前声明\<T\>,静态方法只能使用自身定义的泛型,无法使用类泛型。
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 擦除规则
-
无边界泛型<T>:擦除为 Object
-
有上界泛型<T extends Number>:擦除为 上界类型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"));
}
}
九、泛型与反射工程坑点 & 最佳实践
-
泛型无基本类型:泛型只能用包装类、引用类型,不支持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 生成)