泛型与通配符?的区别
基本定义和用法
定义
?代表未知类型,表示可以接受任何类型。泛型是一个占位符,用于表示具体的类型参数。也就是说?比泛型更抽象。
用法
- 泛型需要在定义类、接口、方法提前指定,不可以直接像通配符只写在方法体中
public static void main(String[] args){
List<T extends Pizza> list = new ArrayList<>();//编译错误
List<?> list1 = new ArrayList<>();
}
需要在定义中指定,如以下定义了一个泛型类
class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
?通配符不可以像泛型直接在方法或类中声明为类型参数,也不能作为返回值
//编译错误
public static ? func(? array) {
}
public static <T> T getFirstElement(T[] array) {
if (array == null || array.length == 0) {
return null;
}
return array[0];
}
类型安全性
泛型提供强类型检查,可以确保传入和操作的类型一致,会更加安全。通配符允许更灵活的类型匹配,但因为类型不确定,不能安全添加元素
public static <T> void processList(List<T> list, T element) {
// 可以读取列表元素,类型为T
for (T elem : list) {
System.out.println(elem);
}
// 添加T类型的元素
list.add(element);
}
public static void processList(List<?> list) {
// 可以读取列表元素,但类型为Object
for (Object elem : list) {
System.out.println(elem);
}
// 不能添加元素,因为类型不确定
// list.add(new Object()); // 编译错误
}
上下界约束
泛型只能使用上界约束,通配符上下界都可以。
泛型上界约束
public <T extends Animal> void processList(List<T> list) {
for (T animal : list) {
animal.eat();
}
}
通配符上下界约束
引用自《Effective Java》
这里有一个助记符来帮助你记住使用哪种通配符类型: PECS 代表: producer-extends,consumer-super。换句话说,如果一个参数化类型代表一个 T 生产者,使用 <? extends T> ;如果它代表 T 消费者,则使用 <? super T>
上界通配符(生产者限制读取)
//? extends T :表示类型必须是`T`或其子类型,用于限制读取操作。
public void processList(List<? extends Animal> list) {
for (Animal animal : list) {
animal.eat();
}
// list.add(new Dog()); // 编译错误,不能添加元素
}
下界通配符(消费者限制写入)
public void addDogs(List<? super Dog> list) {
list.add(new Dog());
// Dog dog = list.get(0); // 编译错误,只能读取为Object
}
总结
-
通配符
?:适用于方法参数类型,灵活但类型不确定,通常用于读取操作(? extends T)或写入操作(? super T)。 -
泛型类型参数
T:适用于类、接口和方法的定义,提供强类型检查和一致性,适合明确类型操作。