通过泛型限制集合只读或只写

在 Java 泛型体系中,? extends T(上界通配符)和 ? super T(下界通配符)是处理泛型集合灵活性与类型安全的核心语法。本文将从添加规则、读取方式、强制转换三个维度,结合实例深入解析这两种通配符的特性,并总结其实际应用场景。

一、上界通配符:List<? extends A>

核心特性:「只读难加"

上界通配符限定集合元素为 AA 的子类(如 A1A2),但编译器无法确定集合具体存储的是哪个子类,因此除了 null,无法添加任何对象;读取时则可通过 AObject 向上转型安全获取。

完整示例与解析

java 复制代码
import java.util.ArrayList;
import java.util.List;
public class GenericExtendsDemo {
    // 定义继承体系
    public static class Parent {}
    public static class A extends Parent {}
    public static class A1 extends A {} // A的子类1
    public static class A2 extends A {} // A的子类2
    public static void main(String[] args) {
        // 声明上界通配符集合:元素为A或A的子类
        List<? extends A> extendsList = new ArrayList<>();
        // ========== 添加操作:编译错误分析 ==========
        // 错误1:无法添加A的子类A1
        // 原因:编译器无法确定extendsList实际存储的是A1还是A2,若允许添加A1,后续添加A2会破坏类型一致性
        extendsList.add(new A1()); // 编译错误
        // 错误2:无法添加父类A本身
        // 原因:上界通配符的本质是「未知的A子类集合」,A并非"未知子类",向下转型不安全
        extendsList.add(new A()); // 编译错误
        // 唯一例外:null是所有引用类型的默认值,无类型冲突
        extendsList.add(null);
        // ========== 读取操作:安全向上转型 ==========
        // 方式1:用A接收(最常用)
        A a = extendsList.get(0);
        // 方式2:用Object接收(所有对象的根类型)
        Object obj = extendsList.get(0);
    }
}

关键原理

上界通配符的核心是「协变」:集合类型可从 List<A1> 安全转型为 List<? extends A>,但代价是失去添加能力 ------ 因为添加操作会打破协变的类型安全。

二、下界通配符:List<? super A>

核心特性:「可加限定,读取需强转"

下界通配符限定集合元素为 AA 的父类(如 ParentObject),编译器能确定 A 及其子类可向上转型为任意父类,因此可添加 AA 的子类;读取时只能默认用 Object 接收,若需用 A 接收则需强制类型转换。

完整示例与解析

java 复制代码
import java.util.ArrayList;
import java.util.List;
public class GenericSuperDemo {
    // 复用继承体系
    public static class Parent {}
    public static class A extends Parent {}
    public static class A1 extends A {}
    public static class A2 extends A {}
    public static void main(String[] args) {
        // 声明下界通配符集合:元素为A或A的父类
        List<? super A> superList = new ArrayList<>();
        // ========== 添加操作:编译规则分析 ==========
        // 错误1:无法添加A的父类Parent
        // 原因:编译器无法确定superList实际存储的是Parent还是Object,添加Parent会破坏类型一致性
        superList.add(new Parent()); // 编译错误
        // 错误2:无法添加顶级父类Object
        // 原因:同理,Object并非"已知的A父类",向上转型超出下界限定
        superList.add(new Object()); // 编译错误
        // 正确1:添加A本身
        // 原因:A可向上转型为任意A的父类(Parent/Object),符合下界限定
        superList.add(new A());
        // 正确2:添加A的子类A1/A2
        // 原因:A1/A2可先向上转型为A,再转型为A的父类,类型安全
        superList.add(new A1());
        // 例外:null仍可添加
        superList.add(null);
        // ========== 读取操作:需处理类型转换 ==========
        // 方式1:默认用Object接收(编译器仅能确定元素是Object的子类)
        Object obj = superList.get(0);
        // 方式2:强制转换为A(需确保集合中实际存储的是A或其子类,否则抛ClassCastException)
        A a = (A) superList.get(0);
    }
}

关键原理

下界通配符的核心是「逆变」:集合类型可从 List<Parent> 安全转型为 List<? super A>,添加 A 及其子类时,因向上转型的特性保证了类型安全;但读取时无法确定具体类型,只能退化为 Object

三、泛型集合的强制转换:边界限定规则

泛型集合的强制转换并非任意,需严格遵循 extends/super 的边界限定 ------ 仅能转换为边界类型或满足边界条件的类型,否则编译器直接报错。

完整示例与解析

java 复制代码
import java.util.ArrayList;
import java.util.List;
public class GenericCastDemo {
    public static class Parent {}
    public static class A extends Parent {}
    public static class A1 extends A {}
    public static void main(String[] args) {
        // ========== 场景1:上界通配符 List<? extends Parent> 的转换 ==========
        List<? extends Parent> extendParentList = new ArrayList<>();
        // 正确:转换为边界类型 Parent(符合"Parent或其子类"的限定)
        List<Parent> parentList1 = (List<Parent>) extendParentList;
        // 错误:Object不是Parent的子类,违反上界限定(extends Parent)
        List<Object> objectList1 = (List<Object>) extendParentList; // 编译或运行时错误
        // ========== 场景2:上界通配符 List<? extends Object> 的转换 ==========
        List<? extends Object> extendObjectList = new ArrayList<>();
        // 正确:Object是所有类的父类,Parent符合"Object或其子类"的限定
        List<Parent> parentList2 = (List<Parent>) extendObjectList;
        // ========== 场景3:下界通配符 List<? super Parent> 的转换 ==========
        List<? super Parent> superParentList = new ArrayList<>();
        // 正确:转换为边界类型 Parent(符合"Parent或其父类"的限定)
        List<Parent> parentList3 = (List<Parent>) superParentList;
        // 正确:Object是Parent的父类,符合下界限定(super Parent)
        List<Object> objectList2 = (List<Object>) superParentList;
        // ========== 场景4:普通泛型集合的转换(无通配符) ==========
        List<Object> rawObjectList = new ArrayList<>();
        // 错误:无通配符的 List<Object> 无法直接转为 List<Parent>
        // 原因:泛型不协变,List<Object> 和 List<Parent> 是完全独立的类型
        List<Parent> parentList4 = (List<Parent>) rawObjectList; // 不建议
    }
}

转换规则总结

通配符类型 可转换类型 不可转换类型
List<? extends T> T 或 T 的子类 T 的父类(除非 T 是 Object)
List<? super T> T 或 T 的父类 T 的子类
无通配符 List<T> 仅 T 本身(强制转换可能抛运行时异常) 其他所有类型(编译器直接报错)

四、实战总结与应用场景

核心结论

1.通配符的核心价值:? extends/super 主要用于方法参数定义,允许接收更宽泛的集合类型,提升方法的通用性。 示例:定义一个打印 A 及其子类的方法

java 复制代码
// 用 extends 接收所有 A 的子类集合,只读不写
public static void printA(List<? extends A> list) {
    for (A a : list) {
        System.out.println(a);
    }
}
// 用 super 接收所有 A 的父类集合,只写不读(或读取后强转)
public static void addA(List<? super A> list) {
    list.add(new A1()); // 安全添加 A 的子类
}

2.方法参数的入参规则:传入的集合类型需符合通配符的边界限定,与前文的添加 / 转换规则完全一致。 3.设计原则:

  • 「生产者(只读)用 extends」:若方法仅读取集合元素,用 ? extends T
  • 「消费者(只写)用 super」:若方法仅添加集合元素,用 ? super T
  • 「既读又写」:避免使用通配符,直接用具体类型 List<T>

避坑提醒

  • 强制转换泛型集合时,编译器仅做语法检查,运行时可能抛出 ClassCastException(如将 List<A1> 强转为 List<A2>);
  • null虽可添加到任意泛型集合,但实际开发中应避免(读取时需判空,否则抛 NullPointerException);
  • 泛型通配符仅作用于方法参数,不建议用于类成员变量或返回值(会丢失类型信息)。

通过理解 extends/super 的边界限定和类型安全规则,可在保证代码灵活性的同时,避免泛型带来的类型转换异常,写出更健壮的 Java 泛型代码。

相关推荐
tealcwu36 分钟前
【Unity技巧】实现在Play时自动保存当前场景
java·unity·游戏引擎
uup37 分钟前
Java 多线程下的可见性问题
java
Pluchon42 分钟前
硅基计划4.0 算法 记忆化搜索
java·数据结构·算法·leetcode·决策树·深度优先
大飞哥~BigFei42 分钟前
deploy发布项目到国外中央仓库报如下错误Project name is missing
java
白羊无名小猪43 分钟前
正则表达式(捕获组)
java·mysql·正则表达式
狂奔小菜鸡44 分钟前
Day23 | Java泛型详解
java·后端·java ee
onejson1 小时前
idea中一键执行maven和应用重启
java·maven·intellij-idea
CoderYanger1 小时前
动态规划算法-简单多状态dp问题:13.删除并获得点数
java·开发语言·数据结构·算法·leetcode·动态规划·1024程序员节
听风吟丶1 小时前
Java 微服务 APM 实战:Prometheus+Grafana 构建全维度性能监控与资源预警体系
java·微服务·prometheus