文章目录
目录
前言
Java 泛型(Generics)是 JDK 5 引入的核心特性,通过参数化类型实现代码的通用性和类型安全。
一、泛型的作用
1.类型安全
在泛型出现前,使用Object
实现通用容器会导致运行时类型转换错误:
List list = new ArrayList();
list.add("Hello");
Integer num = (Integer) list.get(0); // 运行时抛出ClassCastException
List<String> list = new ArrayList<>();
list.add(100); // 编译错误:无法添加整数到String列表
2.通用性
常见的例子(集合)都是泛型接口,为的就是确保通用性
试想,如果ArrayList<Integer>是这样的,那么路就走窄了,其他String,Double怎么办,再写一个ArrayList<String>接口出来?明显不合理,而且自己定义的User,Car这个对象,集合怎么存储呢,只有依靠泛型
这里再举个例子
试想一个场景,有多个接口接收到了N个参数,其中M个是通用参数,V个是特有参数,那么这时候为了通用性,我们定义了一个实体类来存储着N个参数叫BaseNotify类,那么问题来了,BaseNotify这个类中我知道有M个是通用参数,这很好写,那么其他的V个参数怎么存放呢
如果有10个接口,每个接口有V个参数是特有的,不可能就要在一个类中写10*V个字段吧,这不科学,那么泛型就很好解决这个问题
看如下示例,在通用类中再定义一个泛型字段,其中OrderNotify是特有的字段,getBizContentObject方法会将bizContentObj这个JSON数据转为T(OrderNotify)类型,这样就实现通用性了
@Transactional(rollbackFor =Exception.class)
@Override
public void signalAgentPayCallback(BaseNotify<OrderNotify> notify) {
OrderNotify orderNotify;
try {
orderNotify = notify.getBizContentObject(OrderNotify.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
@Data
public class BaseNotify<T> {
// 通知发送时间
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date notifyTime;
// 通知类型
private String notifyType;
// 通知校验ID
private String notifyId;
// 编码格式
private String charset;
// 接口版本
private String version;
// 签名算法类型
private String signType;
// 签名串
private String sign;
// 应用ID
private String appId;
// 业务参数集合 - 存储原始JSON字符串
private String bizContent;
// 已解析的业务参数对象
private transient T bizContentObj;
/**
* 解析bizContent字符串到指定类型的对象
* @param clazz 目标类型
* @return 解析后的对象
* @throws JsonProcessingException 如果解析失败
*/
public T getBizContentObject(Class<T> clazz) throws JsonProcessingException {
if (bizContentObj == null && bizContent != null) {
ObjectMapper mapper = new ObjectMapper();
// 配置 ObjectMapper 以忽略未知字段
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
bizContentObj = mapper.readValue(bizContent, clazz);
}
return bizContentObj;
}
}
@Data
public class OrderNotify {
/**
* 必填: 是
*/
private String orderNo;
/**
* 商户订单号 (支付订单)
* 必填: 是
*/
private String bizOrderNo;
/**
* 必填: 否
* 说明: 退款订单读字段才返回
*/
private String oriOrderNo;
/**
* 原商户订单号
* 必填: 否
* 说明: 退款订单读字段才返回
*/
private String oriBizOrderNo;
}
二、泛型的实现
1.泛型类
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
// 使用
Box<String> stringBox = new Box<>();
stringBox.setContent("Java");
String value = stringBox.getContent(); // 类型安全
2.泛型接口
public interface Comparator<T> {
int compare(T o1, T o2);
}
// 实现
public class StringComparator implements Comparator<String> {
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
}
3.泛型方法
public <T> T getFirstElement(List<T> list) {
return list.isEmpty() ? null : list.get(0);
}
// 使用
List<Integer> numbers = Arrays.asList(1, 2, 3);
Integer first = getFirstElement(numbers); // 自动类型推断
4.T符号的起源(额外)
上面不管是泛型类还是泛型方法看见其中我们用的参数符号都是T,为什么呢
说白了就是约定俗成,既有语义明确性:T(Type)类型,又不是其他关键字
其他字符的使用场景
符号 | 常见用途 | 示例 |
---|---|---|
T | 通用类型(Type) | List<T> , Pair<T> |
E | 集合元素(Element) | List<E> , Set<E> |
K /V | 键值对(Key-Value) | Map<K, V> |
R | 返回值(Result) | <R> R execute() |
? | 通配符(Wildcard) | List<?> , <? extends T> |
三、泛型擦除
Java泛型是编译期的特性,运行时会被擦除为原生类型(Raw Type):
-
List<String>
→List
-
T
→Object
(或边界类型) -
插入强制类型转换:
(String) list.get(0)
限制:
-
不能创建泛型数组:
new T[10]
❌ -
不能实例化类型参数:
new T()
❌ -
静态成员不能使用泛型类型:
class Box<T> {
static T defaultValue; // 编译错误
}
四、泛型通配符
PECS原则(Producer Extends, Consumer Super)
1.上界通配符(<? extends T>
)
用于读取数据(Producer Extends),白话来说就是该类型最高向上转型到T类型(是T类型或者其子类):
public void printNumbers(List<? extends Number> list) {
for (Number num : list) {
System.out.println(num);
}
}
// 可接收List<Integer>, List<Double>等
该泛型类型可以是T
或其子类类型(例如List<? extends Number>
可以接收List<Integer>
、List<Double>
等)。
为什么用于读取?
读取安全:
当使用<? extends T>
时,编译器知道集合中的元素至少是T
类型(或其子类),因此可以安全地将元素当作T
类型读取。
void printNumbers(List<? extends Number> list) {
for (Number num : list) { // 安全读取为Number
System.out.println(num.doubleValue());
}
}
写入不安全:
由于实际类型可能是T
的任意子类(例如Integer
或Double
),无法确定具体类型,因此不能向集合中添加元素(除了null
)。
void addNumber(List<? extends Number> list) {
list.add(100); // 编译错误!无法确定实际类型是否是Integer
list.add(3.14); // 编译错误!无法确定实际类型是否是Double
}
2. 下界通配符(<? super T>
)
用于写入数据(Consumer Super):
public void addNumbers(List<? super Integer> list) {
list.add(100);
list.add(200);
}
// 可接收List<Integer>, List<Number>, List<Object>
泛型类型可以是T
或其父类类型(例如List<? super Integer>
可以接收List<Integer>
、List<Number>
、List<Object>
)。
为什么用于写入?
写入安全:
当使用<? super T>
时,编译器知道集合的元素类型至少是T
的父类,因此可以安全地写入T
类型的元素(因为T
是父类的子类型)。
void fillList(List<? super Integer> list) {
list.add(100); // 安全写入Integer
list.add(200);
}
读取不安全:
由于实际类型可能是T
的任意父类(例如Number
或Object
),读取时只能当作Object
处理,无法确定具体类型。
void readFirst(List<? super Integer> list) {
Integer num = list.get(0); // 编译错误!实际类型可能是Object
Object obj = list.get(0); // 安全,但需要手动类型检查
}
3. 无界通配符(<?>
)
无界通配符 ?
表示“任意类型”,但不关心具体类型,仅允许读取为Object
:
public void processList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
语法特点
- 仅用于引用声明:不能用于泛型类、接口或方法的类型参数定义(如
public class Box<?>
是非法的)。 - 只能出现在方法参数或局部变量中:例如
void printList(List<?> list)
可以说无界通配符?是上届、下届通配符的基础
?和T
可以互相替换吗?答:不行
?:处理完全未知类型,无需关心具体类型(如通用工具方法)
T:当需要操作具体类型(如调用其方法、约束继承关系)时,使用 T
定义泛型类/方法。
无界通配符适用于既不生产也不消费数据的场景(如仅调用 size()
、clear()
)
总结
-
Producer(生产者):
如果泛型对象负责生产数据(如返回元素),使用<? extends T>
,因为需要安全读取。 -
Consumer(消费者):
如果泛型对象负责消费数据(如接收元素),使用<? super T>
,因为需要安全写入。
举例
下面是Collections.copy方法,其中src(P)用于生产读取数据到dest(C)消费者中
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
所以说我们用copy方法的时候
Collections.copy(A,B);就代表将Bcopy到A
特性 | 无界通配符 ? | 上界通配符 ? extends T | 下界通配符 ? super T |
---|---|---|---|
类型范围 | 任意类型 | T 或其子类 | T 或其父类 |
读操作 | 元素视为 Object | 元素视为 T | 元素视为 Object |
写操作 | 仅允许 null | 仅允许 null | 允许 T 或其子类 |
典型场景 | 通用工具方法、只读遍历 | 只读且需类型安全(如计算总和) | 写入数据(如添加元素) |
五、泛型限制与注意事项
-
不能使用基本类型
List<int>
❌ → 使用包装类List<Integer>
✔️ -
不能重载相同原始类型的方法
void print(List<String> list) {}
void print(List<Integer> list) {} // 编译错误:擦除后签名相同