型パラメータAを含むSeq[A]をtoArrayするときには実行時型が必要
昨日のScalaハマリポイント: 型パラメータAを含むSeq[A]をtoArrayするときには実行時型が必要
例えば、次のようなSchool
クラスがあったとする。
このクラスはStudent
もしくはその子クラスのコレクションをメンバに持っている。このクラス設計が嬉しいのは、コレクションの要素型がパラメータになっているおかげで、もし全ての生徒が男子であればstudents
はSeq[Boy]
となり、このように作られたboysSchool
に対してはBoy
だけがもつメンバfightingInstinct
にアクセスできることだ。
ただし、このようなクラス設計をした時にちょっとハマってしまうのが、
School
クラスの内部でSeq[T]
をArray
に変換しようとしたときに、以下のようにコンパイラに怒られることだ。
Error: No ClassTag available for T def studentsAsArray = students.toArray ^ Error: not enough arguments for method toArray: (implicit evidence$1: scala.reflect.ClassTag[T])Array[T]. Unspecified value parameter evidence$1. def studentsAsArray = students.toArray ^
一方で、ShoolTest
でboysSchool.students.toArray
とするぶんには問題ない。
このような現象に初めて出会うと、何のことだかさっぱりだ。
何故このようなことが起こるのかというと、ScalaのArrayは実際のところJavaのArrayであり、
JavaのArrayはプリミティブ型に対して最適化された個別のクラスを用意していて、それ以外の参照型に対するArrayとクラスが違うからである。
つまり、Arrayを作るときにこれらのクラスを作り分けるために型T
がプリミティブ型か参照型かを知る必要があるわけだが、
コンパイル時の型消去のために、School
クラスの内部ではT
が参照型([T <: School]
)であることを知ることができない。
そのため、一体どのArray
を作ればいいかわからない状態に陥ってしまう。
この問題を回避するために、JavaやScalaには実行時に型情報を取得する実行時リフレクションという機構がある。 Scala 2.11では、ClassTag という オブジェクトを使ってコンパイル時に消去されるクラス型を保持しておき、実行時に取得することができる。
上記の問題の場合、以下のように書くことでTのクラス型を保持し、実行時に知ることができるため、無事参照型のArrayを作ることできるということになる。
ClassTagの詳細については、ClassTagトレイトのソースコードも参考になる。
参考: