发布于2026-07-15 阅读(0)
扫一扫,手机访问
Ja va 泛型中的 PECS 原则(Producer Extends, Consumer Super),听起来像是个枯燥的缩写,但在 Caffeine 本地缓存的 CacheWriter 监听器里,它可不是摆设——每个方法签名都严格遵循着类型安全与协变/逆变的逻辑。简单说,就是“谁产出,谁 extends;谁消费,谁 super”。

先从 write 方法说起。你可能会想,为啥 K 和 V 要用 ? super 而不是 ? extends?答案就藏在“消费”这个动作里。key 和 value 都是被监听器接收的输入数据,监听器只负责读取、记录,不负责产出新数据。按照 PECS 的定义,消费者就该用 ? super。举个具体例子:假设你的缓存存的是 String 类型的 key 和 User 类型的 value,但监听器定义成了 CacheWriter。因为 String 是 CharSequence 的子类,而监听器需要能处理所有 CharSequence 及其子类——如果 K 声明为 ? super CharSequence,那传入 String 自然安全。反过来,若错误地用了 ? extends,监听器就只能接收 CharSequence 的子类,但无法保证能处理 CharSequence 本身,直接踩坑。value 同理,业务返回 Employee extends User,监听器声明为 CacheWriter 时,V 的 ? super User 才能让子类实例顺利通过。
再来看 delete(K key, V value, RemovalCause cause)。前两个参数依然是输入,依然是“消费者”,所以 ? super 的规则照旧。但第三个参数 RemovalCause 是个枚举,天生不可变,也没有继承层次——它只是用来标记删除原因(EXPIRED、REPLACED、EXPLICIT 等),属于固定契约。既然不涉及泛型通配,自然就不需要 PECS 来操心。这一点很好理解:枚举类型没有子类型,既不能做生产者,也不能做消费者,直接按原类型声明就行。
虽然 CacheWriter 本身不产出数据,但 Caffeine 的 get(key, mappingFunction) 方法就完美展示了 PECS 的另一面。这里的 mappingFunction 类型是 Function super K, ? extends V>——前半段 ? super K 表示函数可以接受 K 或其父类型(消费),后半段 ? extends V 表示函数返回 V 或其子类型(生产)。这样一来,调用方可以安全地接收结果:比如你声明了 Cache,但函数实际返回 Student extends Person,完全合法。这种设计确保了类型安全,又不牺牲灵活性。
知道了原理,落实到代码里,有几个常见坑需要避开:
new CacheWriter() {...} ,结果缓存实际存的是 Person,编译可能通过,但运行时会因为类型不匹配而出问题。这属于典型的“过度约束”。new CacheWriter,或者直接用 lambda 让类型推导自动适配。如果既要类型安全又想复用,就按“能处理更宽泛类型”的思路设计——比如监听器需要处理所有 User 及其子类,就声明为 CacheWriter super String, ? super User>,而不是窄化到具体子类。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8