詳しいことはこちらを参照 => 型タグとマニフェスト
クラスや関数で実行時に指定される抽象型の情報は、Javaの型消去により判定できなくなってしまう。
例えば以下のScalaコードでは、型T
の情報はコンパイル時に消えている。
class A
class B extends A
class C extends A
def isType[T <: A](a: A) = a.isInstanceOf[T]
// <console>:9: warning: abstract type T is unchecked since it is eliminated by erasure
// def isType[T <: A](a: A) = a.isInstanceOf[T]
// ^
// isType: [T <: A](a: A)Boolean
型消去により意図しない挙動になる可能性があることは、コンパイル時の警告でチェックできる。
実際に動作させてみても、型の検出はできない。
scala> isType[A](new A)
res11: Boolean = true
scala> isType[C](new B)
res12: Boolean = true // 本当は (new B).isInstanceOf[C] => false
ClassTagで型情報を保持する
Scalaの場合、暗黙のパラメータで型の情報を保持することができる。
def isType[T <: A](a: A)(implicit e: scala.reflect.ClassTag[T]) = e.runtimeClass.isInstance(a)
//=> isType: [T <: A](a: A)(implicit e: scala.reflect.ClassTag[T])Boolean
処理中にe.runtimeClass
を使って具体的な Class[T]
を取得し、リフレクションで型チェックを行っている。
scala> isType[C](new B)
res13: Boolean = false
context boundを使う
型パラメータ自体に context bound というシンタックスシュガーが用意されている。
import scala.reflect.ClassTag
def isType[T <: A : ClassTag](a: A) = implicitly[ClassTag[T]].runtimeClass.isInstance(a)
定義している内容としては暗黙のパラメータと同じだが、引数の名前が無く、代わりに暗黙の値を implicitly
で取得する事ができる。
パターンマッチに使う
implicitly
を使って型情報を取得しなくても、パターンマッチで型チェックをすると自動的に処理してくれるようだ。
import scala.reflect.ClassTag
def isType[T <: A : ClassTag](a: A) = a match {
case t: T => true
case _ => false
}
個人的にはこれがシンプルで好み。