我使用的是 Scala 2.10,发现在对泛型参数使用类型推断时出现了我认为很奇怪的行为。考虑下面的例子(注意 i
只是一个虚拟变量):
scala> import scala.reflect.{ClassTag,classTag}
|
| def broken[T : ClassTag](i : Int) : List[T] = {
| println("ClassTag is : " + classTag[T])
| List.empty[T]
| }
import scala.reflect.{ClassTag, classTag}
broken: [T](i: Int)(implicit evidence$1: scala.reflect.ClassTag[T])List[T]
scala> broken(3)
ClassTag is : Nothing
res0: List[Nothing] = List()
scala> val brokenVal : List[String] = broken(3)
ClassTag is : Nothing
brokenVal: List[String] = List()
调用 broken(3)
似乎是一致的,因为函数内的打印语句与推断的 ClassTag
一致。
但是,第二个语句,编译器为 ClassTag
推断并在函数内部打印的内容与实际返回类型之间存在不一致。
我希望编译器从类型声明中推断出 ClassTag
,即 List[String]
。这不对吗?谁能赐教一下?
这里有一些进一步的上下文:我实际上在一些代码中使用类标签来过滤集合(此处未显示),当我没有明确指定 T
时,它显然会失败,因为它是与 Nothing
类型进行比较。
最佳答案
第二个示例的实际返回 类型是List[Nothing]
,因此ClassTag
与它完全一致。发生这种情况是因为 List
是协变的(定义为 List[+A]
而不仅仅是 List[A]
)。这种协方差允许您编写类似 list = Nil
的语句(因为 Nil.type = List[Nothing]
),但它也允许编译器将底部类型推断为列表类型参数,因为 List[Nothing]
是 List[String]
的子类型,同样,由于它的协方差。所以实际的类型不匹配是在返回类型和 brokenVal
的类型之间。
为了克服这个限制,你可以使用不变类型作为你的返回类型,即:
import scala.reflect.ClassTag
class Invariant[T]
def f[T: ClassTag](i: Int): Invariant[T] = {
println("ClassTag: " + implicitly[ClassTag[T]])
new Invariant[T]
}
val ret: Invariant[String] = f(3)
// ClassTag: java.lang.String
// ret: Invariant[String] = Invariant@30af5b6b
另一方面,您可以让编译器从其他内容推断T
,例如,通过传递类型T
的值作为参数。你不能让这个例子在普通 List
上工作,因为你不能改变它们的方差。
关于Scala 泛型类型推断与 ClassTag 不一致,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36456198/