Scala:已声明U为NumberLike[T]子类,为何仍报类型不匹配错误?
Scala类型不匹配困惑:已声明U是NumberLike[T]子类但编译报错
我的代码:
class Statistics[T, U <: NumberLike[T]] { def mean(xs: Vector[U]): U = xs.reduce(_ + _) / xs.size } trait NumberLike[T] { def + (that: NumberLike[T]): NumberLike[T] def / (that: Int): NumberLike[T] }
编译错误:
error: type mismatch; found : NumberLike[T] required: U xs.reduce(_ + _) / xs.size ^ one error found
我搞不懂为什么编译器不接受这段代码——明明已经声明了U是NumberLike[T]的子类啊!
问题出在哪?
你踩了Scala类型系统里一个常见的坑:父类型不能自动向下转换为子类型,哪怕你明确了U <: NumberLike[T]。咱们一步步拆解:
- 先看
xs.reduce(_ + _):xs是Vector[U],里面的元素都是U(NumberLike[T]的子类)。但你的NumberLike[T]特质里,+方法的返回类型是NumberLike[T],而不是U。这意味着,哪怕你把两个U实例加在一起,编译器只会把结果当成通用的NumberLike[T],而不是具体的U类型。 - 接着调用
/ xs.size,这个方法返回的依然是NumberLike[T],但你的mean方法声明要返回U——编译器没办法保证这个NumberLike[T]实例一定是U的实例,所以就报错了。
举个直白的例子:假设你有两个NumberLike[Int]的子类,IntLike和DoubleLike。如果reduce过程中(哪怕是逻辑上不可能,但编译器只认类型声明)返回了DoubleLike,那它肯定不能转换成IntLike(也就是你的U),编译器必须提前阻止这种风险。
怎么修复?
核心思路是让NumberLike的方法返回具体的子类型,而不是父类型。Scala里常用F-bounded多态来实现这个需求,有两种常见写法:
写法1:使用this.type
修改NumberLike特质,让方法返回当前实例的类型:
trait NumberLike[T] { def + (that: NumberLike[T]): this.type def / (that: Int): this.type }
然后实现子类的时候,确保+和/返回当前子类的实例(这里需要用asInstanceOf做一次类型转换,因为构造新实例时编译器无法直接推断为this.type):
class IntLike(val value: Int) extends NumberLike[Int] { override def +(that: NumberLike[Int]): this.type = { new IntLike(this.value + that.asInstanceOf[IntLike].value).asInstanceOf[this.type] } override def /(that: Int): this.type = { new IntLike(this.value / that).asInstanceOf[this.type] } override def toString: String = value.toString }
现在测试一下:
object TestStats { def main(args: Array[String]): Unit = { val stats = new Statistics[Int, IntLike] val nums = Vector(new IntLike(1), new IntLike(2), new IntLike(3)) println(stats.mean(nums)) // 输出2 } }
写法2:带自类型参数的特质(更安全)
这种方式不用强制类型转换,类型检查更严格:
trait NumberLike[T, Self <: NumberLike[T, Self]] { def + (that: Self): Self def / (that: Int): Self } class Statistics[T, U <: NumberLike[T, U]] { def mean(xs: Vector[U]): U = xs.reduce(_ + _) / xs.size } class IntLike(val value: Int) extends NumberLike[Int, IntLike] { override def +(that: IntLike): IntLike = new IntLike(this.value + that.value) override def /(that: Int): IntLike = new IntLike(this.value / that) override def toString: String = value.toString }
这里NumberLike多了一个Self类型参数,明确要求子类把自己作为参数传入,这样+和/方法就能直接返回子类类型,编译器可以完美推断xs.reduce(_ + _)的类型是U,自然就不会报错了。
内容的提问来源于stack exchange,提问作者Dan Li
相关产品推荐
相关产品推荐

