接口实现或扩展何时不应返回比接口声明更具体的返回类型?
1. 泛型不变性限制,根本无法实现协变返回
你提到的协变返回场景能成立,是因为Set<Goblin>确实是Collection<Goblin>的子类型(Set本身继承自Collection,二者泛型参数完全一致),所以可以合法重写。
但如果要让SetMultimap#asMap()返回Map<K, Set<V>>,就会碰到Java泛型不变性的硬限制:Map<K, Set<V>>并不是Map<K, Collection<V>>的子类型(二者泛型参数的第二个类型不同,即使Set<V>是Collection<V>的子类型,Map的泛型参数也不支持协变),下面的代码甚至无法通过编译:
interface Multimap<K, V> { Map<K, Collection<V>> asMap(); } // 编译错误:asMap的返回类型与父接口不兼容,不属于合法重写 interface SetMultimap<K, V> extends Multimap<K, V> { @Override Map<K, Set<V>> asMap(); }
如果强行在子接口中新增一个无参的asMap()方法,就会变成方法重载而非重写,同一个实例在不同声明类型下调用asMap()会返回不同类型的结果,非常容易引发隐性bug。
2. 保持接口体系的一致性
Guava的Multimap体系下还有ListMultimap等其他子接口,如果SetMultimap特殊处理asMap的返回类型,ListMultimap也要做对应修改,会导致整个API设计变得割裂。用户在使用面向父接口Multimap的通用逻辑时,还要额外判断子类型才能确定返回值类型,反而会大幅提升使用成本和出错概率。
3. 公共基础库的兼容性考量
Guava是被全球数百万项目依赖的基础库,任何破坏性的API变更都会导致海量依赖项目编译失败或者运行出错。如果在某个版本中修改SetMultimap#asMap的方法签名,不管是调整返回类型还是新增重载,都会直接破坏旧版本的二进制兼容性,这对公共基础库来说是绝对不能接受的。
4. 显式调用避免误用
提供静态工具方法Multimaps.asMap(SetMultimap)的设计,相当于强制要求开发者显式声明自己需要更具体的Set类型返回值,避免了隐式强转可能带来的类型安全问题。而且这个方法的本质只是做了一次安全的类型转换,完全没有额外的性能开销,和直接返回更具体类型的性能是一致的。
内容的提问来源于stack exchange,提问作者user2601064

