Scala协变单链表add方法编译错误修复请求
解决Scala协变链表中add方法的编译错误
你遇到的这个问题是Scala协变类型使用的经典坑——协变类型参数T出现在了逆变位置(也就是方法的参数列表里)。咱们一步步拆解原因和解决方案:
为什么会报错?
当你给MyLinkedList加上协变标记+T时,意味着如果A是B的子类,那么MyLinkedList[A]也可以被当作MyLinkedList[B]使用。但如果你的add方法接受一个T类型的参数,就会出现逻辑矛盾:
比如有
MyLinkedList[Dog](Dog是Animal的子类),因为协变它可以被转为MyLinkedList[Animal],但此时如果调用add(Cat())(Cat也是Animal的子类),这显然不符合原链表的类型约束,所以Scala编译器会直接阻止这种情况,报错提示协变类型出现在逆变位置。
而如果去掉协变标记,apply方法里的Empty[Nothing]就无法赋值给MyLinkedList[T]了——因为Nothing是所有类型的子类,但没有协变的话,Empty[Nothing]和MyLinkedList[T]是无关类型,编译器不允许这样的赋值。
解决方案:给add方法添加类型下界
调整add方法的签名,引入一个新的类型参数U,让它是T的超类,这样既保留协变特性,又能安全地添加元素:
sealed trait MyLinkedList[+T] { def add[U >: T](element: U): MyLinkedList[U] } object MyLinkedList { def apply[T](elements: T*): MyLinkedList[T] = { if (elements.isEmpty) { new Empty[Nothing] } else { Cons(elements.head, apply(elements.tail: _*)) } } final case class Cons[T](element: T, rest: MyLinkedList[T]) extends MyLinkedList[T] { override def add[U >: T](element: U): MyLinkedList[U] = Cons(element, this) } final case class Empty[T]() extends MyLinkedList[T] { override def add[U >: T](element: U): MyLinkedList[U] = Cons(element, this) } }
为什么这样能解决问题?
- 保留协变特性:
MyLinkedList[+T]的协变逻辑依然生效,MyLinkedList[Dog]可以安全转为MyLinkedList[Animal]。 - 保证类型安全:
add方法的参数类型是U >: T(T的超类),当你给MyLinkedList[Dog]添加一个Animal时,返回的是MyLinkedList[Animal]——这符合逻辑:添加了更宽泛类型的元素后,链表的类型也应该升级为对应的超类。 - apply方法正常工作:因为协变保留了,
Empty[Nothing]作为MyLinkedList[Nothing],可以被转为任何MyLinkedList[T](Nothing是所有类型的子类,协变允许这种向上转换)。
额外说明
这种设计和Scala标准库中的List完全一致——标准库的List也是协变的,它的::方法(类似这里的add)同样使用了类型下界来保证类型安全。
内容的提问来源于stack exchange,提问作者Vadim
相关产品推荐
相关产品推荐

