泛型接口返回类型中<? extends ENTITY>与ENTITY的差异及PECS原则应用疑问
这个问题问得特别准,刚好摸到了泛型设计里协变和PECS原则的细节门道,我来给你拆解清楚:
首先得明确,你现在的两个重写方法都能正常工作,是因为Java的返回类型协变特性——子方法允许返回父方法返回类型的子类型。但Streamable<? extends ENTITY>和Streamable<ENTITY>之间,确实存在容易被忽略的关键差异:
1. 实现灵活性的差异
假设你后续需要扩展一个OtherSome extends Some的子类,然后写一个OtherSomeRepository继承ISomeRepository<Some>:
- 如果父接口的方法定义是
Streamable<? extends ENTITY> findAllByOther(...),那么你完全可以在新的仓库里返回Streamable<OtherSome>或者Page<OtherSome>,不需要修改父接口,编译完全通过。因为Streamable<OtherSome>属于Streamable<? extends Some>的范畴。 - 但如果父接口用的是
Streamable<ENTITY>,那你就没法直接返回Streamable<OtherSome>了——因为泛型是不变的,Streamable<OtherSome>和Streamable<Some>之间没有继承关系,编译器会直接报错,你要么修改父接口的返回类型,要么只能硬返回Streamable<Some>类型的实例。
2. 设计意图的明确性(PECS原则的体现)
这里刚好对应泛型里的PECS原则:生产者用extends,消费者用super。你的这些查询方法都是“生产者”——它们向外提供ENTITY类型的数据,用<? extends ENTITY>相当于给调用者一个明确的信号:
这个方法返回的是ENTITY或者它的某个子类的集合,你只能从中读取数据(消费),别想着往里面加东西(因为你不知道集合的具体类型,添加会有类型安全风险)。
而Streamable<ENTITY>的表述就模糊一些,虽然实际中Streamable可能是只读的,但从泛型定义上,它并没有明确传递“只产出不接受输入”的意图。
3. 类型安全的扩展空间
即使你现在所有实现都只返回ENTITY本身的集合,<? extends ENTITY>的定义也给未来的扩展留了后路。比如哪天你需要优化某个查询,返回ENTITY的子类(比如包含更多字段的EnhancedSome),只要这个子类继承自ENTITY,现有接口完全不需要修改,实现类直接换返回类型就行,不会破坏原有代码的兼容性。
总结一下,虽然在你当前的具体实现场景下两者表现一致,但<? extends ENTITY>在灵活性、设计意图传递和未来扩展性上,都比直接用ENTITY要更周全,是更符合泛型设计最佳实践的写法。
内容来源于stack exchange

