实际开发中的协变与逆变案例:数据处理流水线

在实际开发中,协变(? extends T逆变(? super T 常常出现在处理数据流的场景中。我们通过一个简单的例子------设计一个通用的数据处理流水线,来深入理解它们的使用。


需求描述

假设我们在做一个电商项目,需要处理不同类型的订单数据:

  1. 数据源会提供各种类型的订单(如普通订单、会员订单、促销订单等)。
  2. 我们需要把这些订单传递到处理器进行处理。
  3. 不同的处理器可能对父类或子类的数据有不同的操作需求。

基本类型定义

我们有一个基本的订单类和几个子类:

java 复制代码
// 父类:订单
class Order {
    private String id;
    public Order(String id) { this.id = id; }
    public String getId() { return id; }
}

// 子类:普通订单
class RegularOrder extends Order {
    public RegularOrder(String id) { super(id); }
}

// 子类:会员订单
class MemberOrder extends Order {
    public MemberOrder(String id) { super(id); }
}

设计目标:通用数据处理流水线

我们要设计两个通用的方法:

  1. 读取订单数据的工具(协变场景)。
  2. 将订单数据传递给处理器(逆变场景)。

1. 协变案例:只读订单数据

**场景:**我们需要一个工具方法,可以接受任意类型的订单列表(普通订单、会员订单等),并读取这些订单信息,但不对列表内容做修改。

代码实现:

java 复制代码
// 工具方法:读取订单列表
public static void readOrders(List<? extends Order> orders) {
    for (Order order : orders) {
        System.out.println("订单ID:" + order.getId());
    }
}

协变的意义:

  • ? extends Order 表示"接受 Order 及其子类列表"。
  • 我们只读取列表里的数据,不向列表中添加任何内容。

调用示例:

java 复制代码
List<RegularOrder> regularOrders = List.of(new RegularOrder("R001"), new RegularOrder("R002"));
List<MemberOrder> memberOrders = List.of(new MemberOrder("M001"), new MemberOrder("M002"));

// 读取普通订单
readOrders(regularOrders);

// 读取会员订单
readOrders(memberOrders);

协变的关键点:

  • 允许传入子类型列表(如 List<RegularOrder>)。
  • 只能"读",不能"写"。
    • 如果试图往列表中添加元素,编译器会阻止。
java 复制代码
orders.add(new RegularOrder("R003")); // 编译报错:不能添加数据!

2. 逆变案例:处理订单数据

**场景:**我们有一个订单处理器,需要把普通订单传递进去进行处理操作。由于不同的处理器可能接收更通用的 Order 类型,我们希望代码更灵活。

代码实现:

java 复制代码
// 工具方法:添加订单到处理器
public static void processOrders(List<? super RegularOrder> orders) {
    orders.add(new RegularOrder("R003")); // 安全添加普通订单
    System.out.println("订单已处理!");
}

逆变的意义:

  • ? super RegularOrder 表示"接受 RegularOrder 及其父类的列表"。
  • 我们可以向列表中添加 RegularOrder 或其子类的实例。

调用示例:

java 复制代码
List<Order> allOrders = new ArrayList<>(); // 容器可以是父类类型
processOrders(allOrders);

逆变的关键点:

  • 允许操作父类列表(如 List<Order>)。
  • 只能"写",不能安全地"读"。

为什么不能读?

读取的数据类型不确定,只能当作 Object 处理:

java 复制代码
Object obj = orders.get(0); // OK:只能当作 Object

3. 协变和逆变结合的设计

我们设计一个通用的数据处理流水线,既能从数据源读取订单,又能将订单传递给处理器。

完整实现:

java 复制代码
public static <T> void processPipeline(
    List<? extends T> source, // 协变:只读数据源
    List<? super T> target    // 逆变:只写目标
) {
    for (T item : source) {
        System.out.println("读取订单:" + ((Order) item).getId());
        target.add(item); // 将订单传递到目标列表
    }
}

调用示例:

java 复制代码
List<RegularOrder> regularOrders = List.of(new RegularOrder("R001"), new RegularOrder("R002"));
List<Order> allOrders = new ArrayList<>();

processPipeline(regularOrders, allOrders);

System.out.println("处理后的订单数:" + allOrders.size());

结果输出:

读取订单:R001
读取订单:R002
处理后的订单数:2

总结:协变与逆变的最佳场景

  • 协变(? extends T

    • 适合只读操作,数据来源多样化。
    • 典型场景:数据采集、读取列表内容。
  • 逆变(? super T

    • 适合只写操作,数据目标灵活多样。
    • 典型场景:数据存储、操作父类容器。
  • 协变与逆变结合

    • 适合需要"读取 + 写入"分工明确的场景,如数据流传输、数据转换。

用协变和逆变设计代码,不仅让代码更灵活安全,还能显著提升复用性和可维护性!

推荐阅读文章

相关推荐
蓝黑202011 分钟前
IntelliJ IDEA常用快捷键
java·ide·intellij-idea
Ysjt | 深13 分钟前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
ephemerals__19 分钟前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
码农飞飞23 分钟前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货24 分钟前
Rust 的简介
开发语言·后端·rust
shuangrenlong25 分钟前
slice介绍slice查看器
java·ubuntu
牧竹子25 分钟前
对原jar包解压后修改原class文件后重新打包为jar
java·jar
湫ccc32 分钟前
《Python基础》之基本数据类型
开发语言·python
Matlab精灵33 分钟前
Matlab函数中的隐马尔可夫模型
开发语言·matlab·统计学习
Microsoft Word34 分钟前
c++基础语法
开发语言·c++·算法