java基础-泛型
文章目录
目录
文章目录
前言
一、泛型的作用
1.类型安全
2.通用性
这里再举个例子
二、泛型的实现
1.泛型类
2.泛型接口
3.泛型方法
4.T符号的起源(额外)
三、泛型擦除
四、泛型通配符
为什么用于读取?
为什么用于写入?
语法特点
?和T
总结
举例
五、泛型限制与注意事项
前言
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)@Overridepublic 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;// 通知校验IDprivate String notifyId;// 编码格式private String charset;// 接口版本private String version;// 签名算法类型private String signType;// 签名串private String sign;// 应用IDprivate 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) { // 安全读取为NumberSystem.out.println(num.doubleValue());}
}
写入不安全:
由于实际类型可能是T
的任意子类(例如Integer
或Double
),无法确定具体类型,因此不能向集合中添加元素(除了null
)。
void addNumber(List<? extends Number> list) {list.add(100); // 编译错误!无法确定实际类型是否是Integerlist.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); // 安全写入Integerlist.add(200);
}
读取不安全:
由于实际类型可能是T
的任意父类(例如Number
或Object
),读取时只能当作Object
处理,无法确定具体类型。
void readFirst(List<? super Integer> list) {Integer num = list.get(0); // 编译错误!实际类型可能是ObjectObject 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) {} // 编译错误:擦除后签名相同