学んだことをなぐり書き


ClassManifestでインスタンスの生成

参考
http://www.ne.jp/asahi/hishidama/home/tech/scala/r...
http://www.ne.jp/asahi/hishidama/home/tech/scala/g...
// JavaVM はコンパイルすると型情報が消えてします(type erasure)
// しかし scala には型情報を渡す仕組みが用意されています。

// Predef で定義されている classManifest メソッドを使うと型情報を取得できる。
val int = classManifest[Int]

println(int)
println(int.getClass)

// ClassManifest.erasure メソッドで java.lang.Class を取得できる
println("---------")
println(int.erasure)
println(int.erasure.getClass)
println("---------")

// java.lang.Class.newInstance を使えばインスタンス生成が出来る。
val anyRef = classManifest[AnyRef].erasure
// newInstance は Any を返すので asInstanceOf でキャストしなければならない
// val a: AnyRef = anyRef.newInstance <= error
val a: AnyRef = anyRef.newInstance.asInstanceOf[AnyRef]
println(a)

// インタープリタだと独自のクラスでは newInstance でエラーが出る。
// REPLやコンパイルなら上手く動く。多分パッケージ名が解決できないっぽい。
class C
// classManifest[C].erasure.newInstance <= java.lang.InstantiationException
  
// 引数のあるコンストラクタがある場合は java.lang.Class.getConstructor(s).newInstance(args) を使用する。
val str: String = classManifest[String].erasure.getConstructor(classOf[String]).newInstance("aiueo").asInstanceOf[String]
println(str)

// ここらへんは完全に java の領域っぽい。Int の引数のクラスも Integer じゃないと駄目らしい
// 独自クラスはインタープリタでは動かないので REPL などで試してください。
//class C2(val id: Int)
//classManifest[C2].erasure.getConstructor(classOf[Int]).newInstance(new Integer(10)).asInstanceOf[C2]
Int
class scala.reflect.Manifest$$anon$4
---------
int
class java.lang.Class
---------
java.lang.Object@2c79809
aiueo

ClassManifestで型情報の比較

// コンパイルすると型情報は消えるので isInstanceOf[T] だと全て true になってしまう(Warning も出る)
def check[T](o: AnyRef) {
  println(o.isInstanceOf[T])
}
check[String](new AnyRef)
check[String](List(1, 2, 3))
check[String]("")

println("---------")

// ClassManifest.erasure で取得した java.lang.Class.isInstance を使用すると型のチェックが出来る。
def check2[T](o: Any, cm: ClassManifest[T]) {
  println(cm.erasure.isInstance(o))  
}
check2[String]("", classManifest[String])
check2[String](5, classManifest[String])
check2[String](List(), classManifest[String])

println("---------")

// java.lang.Class 同士をチェックする場合は java.lang.Class.isAssignableFrom を使用する
val m1 = classManifest[String]
val m2 = classManifest[Int]

println(m1.erasure.isAssignableFrom(m1.erasure))
println(m1.erasure.isAssignableFrom(m2.erasure))
warning: there were 1 unchecked warnings; re-run with -unchecked for details
one warning found
true
true
true
---------
true
false
false
---------
true
false

implicitを使用してClassManifestの記述を省略

// 毎回 ClassManifest を引数などに指定するのはウルトラアホらしい。
// implicit を使用すれば大幅に省略できる
class C[T](implicit val cm: ClassManifest[T])

// 自動的に cm = classManifest[String] をしてくれる
val o = new C[String]
println(o.cm)

def func[T](list: List[T])(implicit cm: ClassManifest[T]) {
  println(list)
  println(cm)  
}
func(List(1, 2, 3))

// scala2.8 から導入された「context bound」を使うと
// class C[T](implicit a: A[T]) は class C[T: A] と省略することが出来る。
class C2[T: ClassManifest] {
  // 変数は自動生成されているので取得したい場合は implicitly を使う  
  val cm = implicitly[ClassManifest[T]]
  println(cm)
}
new C2[List[Int]]
java.lang.String
List(1, 2, 3)
Int
scala.collection.immutable.List[Int]

View Bound

参考
http://d.hatena.ne.jp/yuroyoro/20100914/1284471301
scala 2.9.1 の時点では <%< は非推奨で => を使用する。
object Main {
  // A から B に暗黙の型変換して欲しい場合は [A <% B] とする。これを view bound と呼ぶ
  // Int, Float などはそれぞれコンパイル時に RichInt, RichFloat に暗黙の型変換され、そしてその共通のスーパークラスとして ScalaNumberProxy がある。
  // そしてそこに < や > などの関数が定義されている。
  // なので、数値全般に共通した処理を行ないたい場合は [T <% scala.runtime.ScalaNumberProxy[T]] とすれば良い      
  def rangeOfNumber[T <% scala.runtime.ScalaNumberProxy[T]](v: T, min: T, max: T): T =
  {
    // < や > は ScalaNumberProxy に定義されている関数である。
    if ( v.<(min) ) min
    else if (v > max) max
    else v  
  }

  // scala.runtime.ScalaNumberProxy はサポート外というご指摘を受けたので追記修正しました。
  // Ordering を取得して比較するのが正しいお作法のようです。 rangeOfNumber と動作はほぼ一緒です。
  // 一応 View Bound のサンプルとして、rangeOfNumber も置いておきますが、バージョンが変わると動かなくなるかもしれません。
  def rangeOfNumber2[T: Ordering](v: T, min: T, max: T): T = {
    val ord = implicitly[Ordering[T]]

    if (ord.lt(v, min)) min
    else if (ord.gt(v, max)) max
    else v
  }

  val rangeOfInt = rangeOfNumber(_: Int, 5, 10)
  val rangeOfFloat = rangeOfNumber(_: Float, 20.0f, 100.0f)

  def p(v: Any) = println(v)
  
  def main(args: Array[String]) {
    // Symbol は ScalaNumberProxy に暗黙型変換できないのでコンパイル時にエラーが出る!! => type safe :)
    // rangeOfNumber('a, 'b, 'c) <= No implicit view available from Symbol => scala.runtime.ScalaNumberProxy[Symbol].

    p(rangeOfInt(3))
    p(rangeOfInt(100))
    p(rangeOfInt(8))
    
    p("-------------")
    
    p(rangeOfFloat(10.0f))
    p(rangeOfFloat(50.0f))
    p(rangeOfFloat(300.0f))
  }
}
5
10
8
-------------
20.0
50.0
100.0

メンバーのみ編集できます