Java中List<? extends T>与List<? super T>的区别

概述

在Java中,List<? extends T>List<? super T>是用于表示具有泛型参数的列表的类型声明。它们之间的区别在于它们的作用和限制。

  1. List<? extends T>:这表示一个包含T或T的子类的列表。使用extends关键字限定了类型的上界。这种声明方式使得列表只能读取元素,而不能添加新的元素。原因是编译器无法确定要添加的元素类型是否与列表中的类型兼容。例如:
List<? extends Number> numbers = new ArrayList<>();
Number number = numbers.get(0); // 可以读取元素
numbers.add(10); // 编译错误,无法确定要添加的元素类型
  1. List<? super T>:这表示一个包含T或T的超类的列表。使用super关键字限定了类型的下界。这种声明方式使得列表可以添加T类型的元素,但读取元素时只能作为Object类型处理。例如:
List<? super Integer> integers = new ArrayList<>();
integers.add(10); // 可以添加Integer及其子类的元素
Object obj = integers.get(0); // 只能读取为Object类型
Integer integer = integers.get(0); // 编译错误,无法确定列表中的元素类型

总结:

  • List<? extends T>适用于读取元素,无法添加新元素。
  • List<? super T>适用于添加T类型的元素,读取时需要将元素视为Object类型。
  • Java PECS 原则:我们何时使用 extends,何时使用 super 通配符呢?我们可以用Java的PECS 原则:Producer Extends Consumer Super。

这些通配符类型声明允许我们在类型安全的前提下操作具有不同泛型参数的列表。

List<? extends T>

被称作有上界的通配符,当我们使用List<? extends T>时,无法添加新元素的原因涉及到泛型的协变性和类型安全性的考虑。

  1. 泛型协变性(Generics Covariance):

    • 泛型协变性是指对于类型AB,如果BA的子类型,那么List<B>List<A>的子类型。
    • 在Java中,数组是具有协变性的,即B[]A[]的子类型,但泛型是不具备协变性的。这是因为泛型在编译时会进行类型擦除,无法在运行时获得泛型参数的实际类型信息。
  2. 通配符限定的类型:

    • List<? extends T>表示一个未知的类型,该类型是TT的子类型。
    • 当我们声明一个类型为List<? extends T>的变量时,编译器无法确定具体的子类型是什么。它只知道该列表中的元素是TT的子类型,但无法具体指定是哪个子类型。
    • 这种情况下,编译器为了保持类型安全性,禁止我们向列表中添加新元素。因为无法确定要添加的元素与列表中元素的实际类型是否兼容。
  3. 类型不一致的问题:

    • 假设我们可以向List<? extends T>添加新元素,那么我们可能会遇到类型不一致的问题。
    • 如果编译器允许我们添加一个类型为U的元素到List<? extends T>中,而实际列表的元素类型是T的子类型V,那么在读取元素时,我们可能会将一个V类型的元素错误地赋值给类型为U的变量,导致类型错误。
    • 为了避免这种类型不一致的情况,编译器禁止在使用List<? extends T>时添加新元素,以保持类型安全性。

总结:

  • List<? extends T>表示一个未知的TT的子类型的列表,是为了拓展方法形参中类型参数的范围
  • 无法向List<? extends T>添加新元素,因为编译器无法确定列表的具体子类型,为了维持类型安全性,禁止添加操作。
  • 这样做是为了避免类型不一致的问题,即将错误类型的元素放入列表中,导致在读取元素时发生类型错误。
  • 泛型在编译时进行类型擦除,无法在运行时获得泛型参数的具体类型信息,这也是泛型不具备协变性的原因之一。
  • List< Integer > 和 List< Number > 之间不存在继承关系,即不具备协变
    而引入上界通配符的概念后,我们便可以在逻辑上将 List<? extends Number> 看做是 List< Integer > 的父类,可以从逻辑上实现协变,但实质上它们之间没有继承关系。

List<? super T>

被称作有下界的通配符,当使用 List<? super T> 时,只能添加类型为 TT 的子类的元素,这涉及到泛型的逆变性和类型安全性的考虑。

  1. 泛型逆变性(Generics Contravariance):

    • 泛型逆变性是指对于类型 AB,如果 AB 的子类型,那么 List<B>List<A> 的子类型。
    • 在 Java 中,数组是具有逆变性的,即 B[]A[] 的子类型,但泛型是不具备逆变性的。这是因为泛型在编译时会进行类型擦除,无法在运行时获得泛型参数的实际类型信息。
  2. 通配符限定的类型:

    • List<? super T> 表示一个未知的类型,该类型是 TT 的超类。
    • 当我们声明一个类型为 List<? super T> 的变量时,编译器无法确定具体的超类类型是什么。它只知道该列表中的元素是 TT 的超类,但无法具体指定是哪个超类类型。
    • 这种情况下,编译器为了保持类型安全性,限制我们只能添加类型为 TT 的子类的元素。因为只有这样的元素才能确保与列表的类型约束兼容。
  3. 类型不一致的问题:

    • 假设我们可以向 List<? super T> 添加除 TT 的子类之外的元素,那么我们可能会遇到类型不一致的问题。
    • 如果编译器允许我们添加一个不是 TT 的子类的元素到 List<? super T> 中,而实际列表的超类类型是 T 的父类 U,那么在读取元素时,我们可能会将一个 U 类型的元素错误地赋值给类型为 T 的变量,导致类型错误。
    • 为了避免这种类型不一致的情况,编译器限制在使用 List<? super T> 时只能添加类型为 TT 的子类的元素,以保持类型安全性。例如:List<? super Number> 的下界是 List< Number > 。因此,我们可以确定 Number 类及其子类的对象自然可以加入 List<? super Number> 集合中; 而 Number 类的父类对象就不能加入 List<? super Number> 集合中了,因为不能确定 List<? super Number> 集合的数据类型。

总结:

  • List<? super T> 表示一个未知的 TT 的超类的列表。
  • 只能向 List<? super T> 添加类型为 TT 的子类的元素,是为了保证类型安全性,避免类型不一致的问题。
  • 这样做提供了灵活性,允许引用超类类型为 TT 的超类的列表,并安全地添加符合约束的元素。
  • 泛型在编译时进行类型擦除,无法在运行时获得泛型参数的具体类型信息,这也是泛型不具备逆变性的原因之一。
  • List<? super Integer> 在逻辑上表示为 Integer 类以及 Integer 类的所有父类的集合,可以从逻辑上实现逆变,它可以代表 List< Integer>、List< Number >、 List< Object >中的某一个集合,但实质上它们之间没有继承关系。

<T extends Comparable<? super T>>

  • <T extends Comparable<? super T>> 是一个泛型约束(generic constraint),它用于限制泛型类型参数 T。表示T必须是实现了Comparable接口,或者T的父类实现了Comparable接口,拥有更大的灵活性和通用型,更好的体现多态;
  • 而<T extends Comparable> 表示T必须是实现了Comparable接,提供更严格的类型约束,具体可参考

List<?>与List

无限定的通配符的两种表现方式。

  • List<?>:适用于你只需要从列表中读取数据,而不需要向列表中添加数据的场景。它提供了最大程度的灵活性,因为它可以接受任何类型的List。
  • List:适用于你需要读取和添加特定类型数据的场景。它提供了类型安全,因为编译器可以确保列表中的所有元素都是类型T。类型参数T不但可以在方法参数中使用,也可以在方法返回值和方法体内使用

示例1:

//    public static void test01(List list) {
    public static void test01(List<?> list) {//同上
        System.out.println(list);
    }

    public static <T> void test02(List<T> list,T t) {//类型参数T不但可以在方法参数中使用,也可以在方法返回值和方法体内使用
        list.add(t);
        T t1 = list.get(0);
        System.out.println(list);
    }

示例2:

    public static void test01(List<? extends Number> list) {
        System.out.println(list);
    }

    public static <T extends Number> void test02(List<T> list,T t) {//类型参数T不但可以在方法参数中使用,也可以在方法返回值和方法体内使用
        list.add(t);
        T t1 = list.get(0);
        System.out.println(list);
    }

示例

泛型类

class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
class Container<T> {
    private List<? super T> items = new ArrayList<>();

    public void addItem(T item) {
        items.add(item);
    }

    public void addItems(List<? extends T> itemList) {
        items.addAll(itemList);
    }

    public List<? super T> getItems() {
        return items;
    }
}

Container<Animal> animalContainer = new Container<>();
animalContainer.addItem(new Dog());
animalContainer.addItem(new Cat());

List<Dog> dogList = new ArrayList<>();
dogList.add(new Dog());
dogList.add(new Dog());

animalContainer.addItems(dogList);

List<? super Animal> items = animalContainer.getItems();

在这个例子中,Container 类是一个泛型类,使用 List<? super T> 作为成员变量的类型。我们可以向 Container 对象中添加 T 类型或 T 的子类的元素,并且可以通过 addItems 方法添加 List<? extends T> 类型的列表。通过 getItems 方法获取到的列表类型是 List<? super T>。

这些示例展示了 <? super T> 的应用场景,它可以用于方法参数、泛型类中的成员变量等,提供了更大的灵活性和多态性,允许处理不同类型的列表并进行元素操作。

泛型方法

public static <T> void addElements(List<? super T> list, T[] elements) {
    for (T element : elements) {
        list.add(element);
    }
}

List<Object> objectList = new ArrayList<>();
String[] strings = {"Hello", "World"};
addElements(objectList, strings);  // 向 Object 类型的列表添加 String 类型的元素

在这个例子中,addElements 是一个泛型方法,接受一个 List<? super T> 类型的列表和一个 T 类型的数组。我们可以将不同类型的数组元素添加到列表中,只要它们是 T 类型或 T 的子类。

泛型接口

interface Eater<T> {
    void eat(List<? super T> foodList);
}

class AnimalEater<T> implements Eater<T> {
    public void eat(List<? super T> foodList) {
        for (Object food : foodList) {
            // 进行吃食物的操作
        }
    }
}

List<Object> foodList = new ArrayList<>();
foodList.add("Meat");
foodList.add("Fish");

Eater<String> eater = new AnimalEater<>();
eater.eat(foodList);  // AnimalEater 实例可以接受 List<Object> 类型的食物列表

在这个例子中,Eater 是一个泛型接口,定义了一个 eat 方法,接受一个 List<? super T> 参数。AnimalEater 类实现了 Eater 接口,并针对不同类型的食物列表进行吃食物的操作。通过使用 <? super T>,我们可以在实现类中接受不同类型的食物列表,只要它们是 T 类型或 T 的超类。

这些示例展示了 <? super T> 在泛型方法和接口实现中的应用,它使得代码更加灵活,能够处理不同类型的数据并进行相应的操作。

泛型方法,多重限定

//public static <T extends Number & Comparable<? super T>> T findMax(List<? extends T> list) {//list只可读,不可写
//public static <T extends Comparable<? super T>> T findMax(List<? extends Number> list) { //同上,list只可读,不可写
public static <T extends Number & Comparable<? super T>> T findMax(List<T> list) { //list可读,可写
    if (list == null || list.isEmpty()) {
        throw new IllegalArgumentException("List is empty or null");
    }

    T max = list.get(0);
    for (T item : list) {
        if (item.compareTo(max) > 0) {
            max = item;
        }
    }
    list.add(max);
    return max;
}

这是一个泛型方法 findMax,它接受一个类型参数 T,它必须满足以下条件:

  • T 必须是 Number 类型或其子类型。
  • T 或者其父类必须实现了 Comparable 接口,并且可以与类型 T 的对象进行比较。
    该方法的目的是在给定的列表 list 中找到最大的元素,并返回它。
  • 11
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java的泛型,`<? extends T>`和`<? super T>`是用来限定通配符(Wildcard)的上界和下界。 1. `<? extends T>`:表示通配符的上界是T或T的子类。使用`<? extends T>`可以使泛型类型接受T或T的子类型作为参数,但不能用于写入对象。 ```java public void processList(List<? extends Number> list) { for (Number num : list) { System.out.println(num); } } List<Integer> integers = new ArrayList<>(); integers.add(1); integers.add(2); processList(integers); // 可以传递List<Integer>或List<Number>,但不能传递List<Object> ``` 在上述示例,`processList()`方法接受一个`List<? extends Number>`类型的参数,这意味着可以传递`List<Integer>`或`List<Number>`作为参数。在方法内部,我们可以从list读取Number类型的元素,因为Number是Integer的父类。 2. `<? super T>`:表示通配符的下界是T或T的父类。使用`<? super T>`可以使泛型类型接受T或T的父类型作为参数,并且可以用于写入对象。 ```java public void addToList(List<? super Integer> list) { list.add(1); list.add(2); } List<Number> numbers = new ArrayList<>(); numbers.add(0.5); addToList(numbers); // 可以传递List<Integer>或List<Object>,但不能传递List<Number> System.out.println(numbers); // 输出:[0.5, 1, 2] ``` 在上述示例,`addToList()`方法接受一个`List<? super Integer>`类型的参数,这意味着可以传递`List<Integer>`或`List<Object>`作为参数。在方法内部,我们可以向list添加Integer类型的元素,因为Integer是Number的子类。 总结: - `<? extends T>`用于限定泛型的上界,可以读取泛型对象,但不能写入; - `<? super T>`用于限定泛型的下界,可以写入泛型对象,但读取时需要进行类型转换。 使用通配符的目的是为了增加泛型的灵活性,在不确定具体类型的情况下,能够处理更广泛的数据类型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值