泛型上下限

泛型上下限

泛型上下限

Java 基础 - 泛型机制详解 | Java 全栈知识体系

<?> 无限制通配符
<? extends E>     extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类
<? super E>       super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类

// 使用原则《Effictive Java》
// 为了获得最大限度的灵活性,要在表示 生产者或者消费者 的输入参数上使用通配符,使用的规则就是:生产者有上限、消费者有下限
1. 如果参数化类型表示一个 T 的生产者,使用 < ? extends T>;
2. 如果它表示一个 T 的消费者,就使用 < ? super T>;
3. 如果既是生产又是消费,那使用通配符就没什么意义了,因为你需要的是精确的参数类型。

为什么 List<? extends A> 不能添加元素?

1. 通配符的实际含义

List<? extends A> 表示:

  • "这是一个 List,其元素类型是 某个未知的 A 的子类型"
  • 可能是 List<A>,也可能是 List<B>,或者 List<C>(如果 C 也 extends A)

2. 编译器不知道具体类型

考虑这个场景:

class A {}
class B extends A {}
class C extends A {}

public static void addToList(List<? extends A> list) {
    // 编译器不知道 list 的实际类型是什么!
    // 可能是 List<A>、List<B> 或 List<C>
    
    // 如果允许这样:
    list.add(new B());
    
    // 但调用方可能传入的是 List<C>!
    List<C> listC = new ArrayList<>();
    addToList(listC);  // 如果允许 add,这里就会把 B 对象加入 List<C>!
}

3. 类型安全问题

public static void main(String[] args) {
    List<B> listB = new ArrayList<>();
    
    // 假设允许添加
    someMethod(listB);
    
    // 现在 listB 中可能有非 B 类型的对象!
    B b = listB.get(0);  // 可能发生 ClassCastException
}

public static void someMethod(List<? extends A> list) {
    // 如果允许:
    list.add(new C());  // C 也是 A 的子类,但不是 B!
    // 这会破坏 listB 的类型安全
}

三种情况的对比

通配符类型 含义 能否添加 能否读取 类型安全
List<?> 未知类型 不能(除 null) 作为 Object 安全
List<? extends A> A 或 A 的子类 不能(除 null) 作为 A 安全
List<? super A> A 或 A 的父类 能添加 A 及其子类 作为 Object 安全

记忆技巧

"extends 只出不进,super 只进不出"

  • ? extends T生产者,只能从中读取(产出 T)
    • 不能添加(除 null)
    • 读只能按T来读。因为T和T的子类都是T
  • ? super T消费者,只能向其添加(消费 T)
    • 下界通配符 (? super T) 的添加规则
      • 可以添加:T 和 T 的子类
      • 不能添加:T 的父类或无关类型
    • 读只能按Object来读。因为T和T的父类都是Object

实际使用建议

  1. 需要读取 → 用 ? extends T
  2. 需要写入 → 用 ? super T
  3. 既读又写 → 不要用通配符,用具体类型 <T>
  4. 完全通用,不关心类型 → 用 ?

示例说明

import java.util.ArrayList;
import java.util.List;

class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}

public class Test {
    // 生产者 - 只能读
    public static void processAnimals(List<? extends Animal> animals) {
        for (Animal a : animals) {  // OK - 读取
            System.out.println(a);
        }
//        animals.add(new Dog());  // 编译错误!
//        animals.add(new Cat());  // 编译错误!
        // 编译器不知道 list 的实际类型是什么!可能是Animal及其子类,因此可能是 List<Animal>、List<Dog> 或 List<Cat>
        // 如果animals是List<Animal>那没问题,可以加Dog和Cat
        // 如果animals是List<Dog>,那就不能加animals.add(new Cat());
        // 如果animals是List<Cat>,那就不能加animals.add(new Dog());
        // 底层逻辑的核心:编译器在进行类型检查时,必须保守地假设最坏情况。animals不是List<Animal>,就出现问题了。
        // 对于<? extends T>,它不知道具体的类型参数是什么,为了保证绝对的类型安全,它必须禁止任何可能破坏类型安全的操作。
        // 这种保守策略确保了"一次编写,到处运行"的类型安全性。
    }
    
    // 消费者 - 只能写
    public static void addDogs(List<? super Dog> dogs) {
        // 无论实际是什么列表,Dog 都能安全放入
        dogs.add(new Dog());
        // Dog → Dog(如果 list 是 List<Dog>)
        // Dog → Animal(如果 list 是 List<Animal>)
        // Dog → Object(如果 list 是 List<Object>)
        // 编译器不知道 list 的实际类型是什么!可能是 List<Dog>、List<Dog的父类>
        // 这里Dog的父类有Animal,object,因此可能是 List<Dog>、List<Animal>、List<Object>
        // 无论实际是什么列表,Dog 都能安全放入。但是Animal、Object不能安全放入

        // 因为可能是List<Dog>、List<Animal>、List<Object>
        // 取出来只能作为 Object取出,因为Dog、Animal、Object都是Object
        Object object = dogs.get(0);  // 编译成功!只能作为 Object
//        Dog d = dogs.get(0);  // 编译错误!只能作为 Object
    }
    
    public static void main(String[] args) {
        List<Dog> dogList = new ArrayList<>();
        processAnimals(dogList);  // OK

        List<Animal> list = new ArrayList<>();
        list.add(new Dog());
        list.add(new Cat());
        list.add(new Animal());
        processAnimals(list);  // OK

        List<Animal> animalList = new ArrayList<>();
        addDogs(animalList);  // OK - Animal 是 Dog 的父类
    }
}

这种设计确保了类型安全,虽然有些限制,但防止了运行时出现 ClassCastException

posted @ 2025-12-02 20:50  deyang  阅读(39)  评论(0)    收藏  举报