scala ClassTag剖析
概述
最近在学习kafka的源代码,按照建议从KafkaApi进行入手,对于业务流程的话基本是清楚的,不过在阅读源码的时候发现有些语法实在是晦涩难懂,因此在网上多方查证并记录,以便后续可以快速的拾起来。
详解
废话不多说直接看代码:
val produceRequest = request.body[ProduceRequest]该过程会调用如下方法:
1 | def body[T <: AbstractRequest](implicit classTag: ClassTag[T], nn: NotNothing[T]): T = { |
首先我们从方法入手,上述方法的定义包含了两个参数,不过在调用的时候并没有将这两个参数传入,这是因为参数列表是隐式参数的一种写法,这种写法会将implicit后面的参数都作为隐式变量,既然作为隐式变量,相应的就需要有定义并导入这些隐式参数的地方,对于classTag是由编译器传入的隐式变量,而nn则是通过NotNothing的伴生对象传入的。
解释完这个方法的调用,我们继续看一下这个方法本身,可以看到这是一个泛型方法,并且泛型参数指定了类型的上界,不过在jvm的运行态中这种泛型方法会被擦除,因此如果需要保存运行时的类型信息就需要特定的容器进行保存,这里的ClassTag[T]的作用就在于保存参数类型,对于这种写法还有一种上下文界定的写法即:[T : ClassTag],其表示使用ClassTag接收T类型的参数,这样我们就可以在运行的时候获取到对应的类型,不过你可能会想怎么获取类型呢?这就需要implicitly[ClassTag]来查看对应的隐式变量了(需要注意这里并不是implicit,两者是不一样的,implicit是关键字,implicitly是函数,其接收一个类型参数,并返回当前域内的该类型的隐式的值,可以发散一下,如果域内有两个类型相同的隐式的值则必定会产生错误,具体是什么错误可以自己试一下),我们举个例子如下:
1 | import scala.reflect.ClassTag |
对应结果如下:
1 | import scala.reflect.ClassTag |
另外我们可以看一下上下文界定这种写法直接在编译器上显示的结果:

可以看到编译器会自动的将上述函数自动的转化成带有隐式变量参数的函数。另外有需要强调的是,参数是有上界的,这里对应的上界是AbstractRequest,这样我们是否还可以使用上下文界定呢?答案是可以的,如下:
1 | sealed trait TT |
最终结果输出如下:
1 | defined trait TT |
更一般的,上下文界定的右边的类型不止可以是ClassTag,还可以是任意的泛型类,其代表的含义是传入一个隐式的该类型的参数,具体可以参考scala那些事中的Ordering。
等等,好像还有一点遗漏了,函数的参数有两个,一个是ClassTag,一个是NotNoting,第二个参数起到什么作用?
1 | import scala.annotation.implicitNotFound |
- def body[T <: AbstractRequest](implicit classTag: ClassTag[T], nn: NotNothing[T]): T
1
2
3
4
5
6
7
8
9
10
11
12
13*
* If we remove the `nn` parameter and we invoke it without any type parameters (e.g. `request.body`), `Nothing` would
* be inferred, which is not desirable. As defined above, we get a helpful compiler error asking the user to provide
* the type parameter explicitly.
*/
@implicitNotFound("Unable to infer type parameter, please provide it explicitly.")
trait NotNothing[T]
object NotNothing {
private val evidence: NotNothing[Any] = new Object with NotNothing[Any]
implicit def notNothingEvidence[T](implicit n: T =:= T): NotNothing[T] = evidence.asInstanceOf[NotNothing[T]]
}
基本可以预测其用途也是用于上下文界定的,只是在调用方法的时候如果不传递类型参数的话,我们需要显示的表示出来,说实话,我暂时还没有理解这句话的意思,后续再慢慢搞吧
TODO
1 | scala> def ct2[T<: TT : ClassTag : Ordering](t: T) = { |