Java 泛型完整知识点笔记
一、泛型定义 & 基本使用
泛型从 JDK 1.5 开始引入:
- 在编译阶段完成类型校验,类型错误编译时期就报错;
- 避免频繁强制类型转换;
- 只作用于编译阶段。
无泛型(存在类型转换异常风险)
@Test
public void testGeneric() {
ArrayList list = new ArrayList();
list.add("China");
list.add(1); // 可以存入任意类型,无法限制
// 取出必须强转,容易发生 ClassCastException
String str = (String) list.get(0);
}
使用泛型(类型约束 + 无需强转)
@Test
public void testGeneric2() {
// 指定集合元素只能是 String
ArrayList<String> list = new ArrayList<String>();
list.add("China");
// list.add(1); // 编译报错,提前拦截错误
// 取出直接就是 String,不用强制转换
String str = list.get(0);
}
泛型书写规则
@Test
public void test3() {
// 规范写法
List<Object> list1 = new ArrayList<Object>();
// JDK7 菱形语法:右侧泛型可以省略
List<String> list3 = new ArrayList<>();
// 只右侧写泛型无效,整体还是原生集合
List list4 = new ArrayList<String>();
list4.add(1);
// 左右泛型类型不一致,编译报错
// List<Object> list5 = new ArrayList<String>();
// 泛型只能写引用类型,基本数据类型不行
// List<int> list6 = new ArrayList<>();
}
泛型擦除
- 泛型只在编译期生效,编译生成 class 字节码后,泛型标记会被擦除;
- 目的:保证 JDK 向下兼容老版本无泛型代码。
验证泛型擦除
List<String> list1 = new ArrayList<String>();
List<Integer> list2 = new ArrayList<Integer>();
Class c1 = list1.getClass();
Class c2 = list2.getClass();
// 运行时两者都是 ArrayList.class,输出 true
System.out.println(c1 == c2);
运行期间不存在 ArrayList<String>、ArrayList<Integer> 的区分,统一为 ArrayList。
泛型方法 / 泛型类 / 泛型接口
作用:抽取通用代码,制作通用工具、通用DAO,提升代码复用性与灵活性。
泛型方法
在方法上声明泛型<K,T>,调用方法时自动确定泛型类型。
public class GenericDemo2 {
// 普通重载方法,扩展性差
public Student add1(Student student, Teacher teacher) {
return null;
}
// 泛型方法:<K,T> 声明泛型
public <K,T> K add(K k, T t) {
return k;
}
@Test
public void test1() {
// 调用时自动推导类型
Float result1 = add(1.0f, 1);
System.out.println(result1);
String result2 = add("abc", 1);
System.out.println(result2);
}
}
泛型类
在类名上定义泛型,整个类内部均可使用该类型,不用每个方法单独声明。
// 通用DAO模板
public class BaseDao <T>{
private List<T> list = new ArrayList<>();
public void add(T t) {}
public void update(T t) {}
public T selectById(Integer id) {
return null;
}
}
@Test
public void test2() {
// 操作Student
BaseDao<Student> baseDao1 = new BaseDao<>();
baseDao1.add(new Student());
// 复用同一个类,操作Teacher
BaseDao<Teacher> baseDao2 = new BaseDao<>();
baseDao2.add(new Teacher());
}
泛型接口
定义泛型接口
public interface IBaseDao<T> {
void add(T t );
void update(T t );
}
实现类确定泛型类型
// 实现接口时直接指定泛型为 Person
public class PersonDao implements IBaseDao<Person> {
}
// 指定泛型为 Student
public class StudentDao implements IBaseDao<Student> {
}
泛型通配符与限定符
泛型字母命名约定
只是命名习惯,任意大写字母均可:
| 标识 | 含义 |
|---|---|
| T(Type) | 任意实体类型 |
| E(Element) | 集合中的元素 |
| K / V | 键值 Key / Value |
| N(Number) | 数值类型 |
| ? | 未知类型、通配符 |
? 无界通配符
List<?> 代表任意类型的 List,可以接收任意泛型集合,只能读取、不能新增元素,保证安全。
public void add(List<?> list) {
// list.add(xxx); 无法添加元素
}
? extends 上限限定(上边界)
? extends Number:集合元素只能是 Number 及其子类(Integer、Double、Float等)。 只能读取,不能写入。
// 元素只能是 Number 的子类
public void add(List<? extends Number> list) {
}
@Test
public void testGeneric23() {
List<Double> list1 = new ArrayList<>();
List<Integer> list3 = new ArrayList<>();
add(list1);
add(list3);
// List<String> 不属于Number子类,编译报错
// List<String> list4 = new ArrayList<>();
// add(list4);
}
? super 下限限定(下边界)
? super String:集合元素只能是 String 本身或 String 的父类。 可以写入元素。
// 元素只能是String 或 String的父类
public void add(List<? super String> list) {
}
@Test
public void testGeneric24() {
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
add(list1);
add(list2);
// Integer不是String父类,报错
// List<Integer> list3 = new ArrayList<>();
// add(list3);
}
上下限总结
1.
<? extends T>:上限,可读不可写;
2.<? super T>:下限,可写可读;
3.<?>:无界,可读不可写。
项目中常用泛型实体
统一返回结果类 Result
后端统一返回格式,data 泛型适配任意返回数据:
public class Result<T> {
public static final int ERROR = 1;
public static final int OK = 0;
private Integer code; // 状态码 0成功 1失败
private String msg; // 提示信息
private T data; // 返回的数据,泛型适配任意类型
}
分页实体 PageInfo
分页通用模板,泛型适配不同实体分页:
public class PageInfo<T> {
private Integer pageNo;
private Integer pageSize;
private List<T> list;
private Integer totalPage;
}
核心总结
- 泛型作用:编译期类型检查、免去强制类型转换;
- 编译后发生泛型擦除,运行不存在泛型;
- 泛型类/接口用于抽取通用模板(DAO、分页、统一返回体);
extends限定上限、super限定下限,用来控制集合元素类型范围。