Caffeine 的 CacheWriter.write 方法采用 ? super K 和 ? super V 是因 key 和 value 均为被消费的输入,遵循 PECS 中“Consumer Super”原则以支持子类型安全传入;delete 方法前两参数同理,RemovalCause 为不可变枚举,不涉泛型协变。

PECS(Producer Extends, Consumer Super)是 Java 泛型中关于通配符使用的重要原则,它指导我们如何安全地读写泛型集合或回调接口。在 Caffeine 的 CacheWriter 监听器中,这一原则直接影响其方法签名的设计逻辑——不是随意而为,而是严格遵循类型安全与协变/逆变语义。
为什么 write 方法用 ? super K 和 ? super V
CacheWriter.write(K key, V value) 实际上在源码中被声明为:void write(@Nullable Object key, @Nullable Object value)
但若你自定义泛型实现(如 CacheWriter<string user></string>),其泛型参数的约束必须符合 PECS:key 和 value 都是被缓存“消费”的数据,即监听器只接收、不产出,因此应使用 ? super 语义。
- key 是写入动作的输入,监听器要处理任意子类型(如传入
String,但监听器可能声明为CacheWriter<CharSequence, User>),所以K应满足? super CharSequence才能兼容; - 同理,value 是被写入的值,若业务返回
Employee extends User,而监听器定义为CacheWriter<String, User>,它仍需能接收该子类实例——这正依赖V声明为 consumer 角色,用? super User才安全。
delete 方法中的 RemovalCause 为何不涉及 PECS
delete(K key, V value, RemovalCause cause) 的前两个参数同样是输入,同样适用 Consumer Super;而 RemovalCause 是枚举类型,不可变且无继承层次,不涉及泛型通配,因此无需 PECS 考量。它的存在只是为了区分删除原因(如 EXPIRED、REPLACED、EXPLICIT),属于固定契约,与类型协变无关。
对比 get 方法体现 Producer Extends
虽然 CacheWriter 本身不产出数据,但 Caffeine 的 get(key, mappingFunction) 方法就体现了 PECS 的另一面:mappingFunction 类型为 Function super K, ? extends V> ——
其中 ? super K 表示函数可接受 K 或其父类型(消费者);? extends V 表示函数返回 V 或其子类型(生产者),确保调用方能安全接收结果(如声明 Cache<String, Person>,函数却返回 Student extends Person,完全合法)。
实际编码中如何避免 PECS 错误
定义监听器时,不要强行绑定具体子类型:
- ❌ 错误写法:
new CacheWriter<String, Employee>() {...}→ 若缓存实际存的是Person,编译可能通过但运行时类型不匹配; - ✅ 推荐写法:
new CacheWriter<Object, Object>() {...}或直接用 lambda,让类型推导自动适配; - 若需类型安全又想复用,应按“能处理更宽泛类型”的思路设计:例如监听器处理所有
User及其子类,则声明为CacheWriter<? super String, ? super User>,而非窄化。


















