泛型与通配符?的区别

189 阅读2分钟

泛型与通配符?的区别

基本定义和用法

定义

?代表未知类型,表示可以接受任何类型。泛型是一个占位符,用于表示具体的类型参数。也就是说?比泛型更抽象。

用法

  1. 泛型需要在定义类、接口、方法提前指定,不可以直接像通配符只写在方法体中
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;
    }
}
  1. ?通配符不可以像泛型直接在方法或类中声明为类型参数,也不能作为返回值
//编译错误
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:适用于类、接口和方法的定义,提供强类型检查和一致性,适合明确类型操作。