Kotlin中逆变的必要性:无逆变实现的代码缺陷探讨
我从一篇Kotlin泛型修饰符的文章中学到了很多,但有个疑问:没发现使用逆变(contravariance)的明显优势。比如下面是用逆变的代码:
使用逆变的实现
interface Message interface OrderManagerMessage : Message interface InvoiceManagerMessage : Message class Order {} class OrderManagerMessageImpl : OrderManagerMessage {} class InvoiceManagerMessageImpl : InvoiceManagerMessage {} class AddOrder(val order: Order) : OrderManagerMessage {} class CancelOrder(val orderId: String) : OrderManagerMessage {} class MakeInvoice(val order: Order) : OrderManagerMessage class Connection(val url: String) { fun send(msg: Message) {} } interface Sender<in T : Message> { fun send(message: T) } class GeneralSender(serviceUrl: String) : Sender<Message> { private val connection = Connection("") override fun send(message: Message) { connection.send(message) } } fun main() { val orderManagerSender: Sender<OrderManagerMessage> = GeneralSender("orderManagerURL") orderManagerSender.send(OrderManagerMessageImpl()) val invoiceManagerSender: Sender<InvoiceManagerMessage> = GeneralSender("invoiceManagerURL") invoiceManagerSender.send(InvoiceManagerMessageImpl()) }
我们也可以不用逆变,只给GeneralSender添加泛型约束就能实现相同功能:
不使用逆变的实现
interface Sender<T : Message> { fun send(message: T) } class GeneralSender<T : Message>(serviceUrl: String) : Sender<T> { private val connection = Connection("") override fun send(message: T) { connection.send(message) } } fun main() { val orderManagerSender: Sender<OrderManagerMessage> = GeneralSender("orderManagerURL") orderManagerSender.send(OrderManagerMessageImpl()) val invoiceManagerSender: Sender<InvoiceManagerMessage> = GeneralSender("invoiceManagerURL") invoiceManagerSender.send(InvoiceManagerMessageImpl()) }
想请教这种修改后的代码存在哪些缺陷?
不使用逆变的实现存在的缺陷:
不必要的实例冗余
每次创建GeneralSender实例时,都会生成一个绑定具体T的泛型实例。而逆变版本只需要一个基于Message的GeneralSender单实例,就能适配所有Sender<Message子类型>的需求,避免重复创建逻辑完全一致的对象,节省内存和初始化开销。API语义模糊
逆变的in修饰符能直接传递语义:这个Sender是消费T类型的,只接收T作为输入,不会产出T类型数据。不带逆变的泛型接口无法传递这种明确语义,读者必须查看方法签名才能判断使用场景,增加了理解成本。扩展与维护受限
如果后续GeneralSender需要添加统一的Message级逻辑(比如通用日志、消息预处理),泛型版本要确保逻辑对所有T : Message都适配;而逆变版本直接基于Message实现,逻辑更统一,无需考虑泛型类型差异。另外,传递Sender实例时,逆变版本灵活性更高——可以直接把Sender<Message>赋值给Sender<OrderManagerMessage>,但不带逆变的Sender<T>做不到这种向上兼容的赋值,除非显式转型。潜在类型安全风险
当前代码逻辑简单,但如果后续有人修改GeneralSender,添加依赖T的特定逻辑(比如强制把T转型为某个子类),泛型版本很容易引入类型转换错误;而逆变版本基于Message实现,逻辑边界清晰,这类问题出现的概率更低。
内容的提问来源于stack exchange,提问作者Archsx

