Scala範型

轉自:http://www.cnblogs.com/czh-liyu/p/4694395.html

8
99
100
101
102
103
104
package com.parllay.scala.type_parameterizitor
 
/**
* Created by richard on 15-7-30.
* 第46講: ClassTag 、Manifest、ClasMainifest TagType實戰
*/
object Manifest_Class {
 
def main(args: Array[String]) {
/**
Manifest是scala2.8引入的一個特質,用於編譯器在運行時也能獲取泛型類型的信息。
在JVM上,泛型參數類型T在運行時是被“擦拭”掉的,編譯器把T當作Object來對待,
所以T的具體信息是無法得到的;爲了使得在運行時得到T的信息,
scala需要額外通過Manifest來存儲T的信息,並作爲參數用在方法的運行時上下文。
 
def test[T] (x:T, m:Manifest[T]) { ... }
有了Manifest[T]這個記錄T類型信息的參數m,在運行時就可以根據m來更準確的判斷T了。
但如果每個方法都這麼寫,讓方法的調用者要額外傳入m參數,非常不友好,且對方法的設計是一道傷疤。
好在scala中有隱式轉換、隱式參數的功能,在這個地方可以用隱式參數來減輕調用者的麻煩。
 
這裏給出了一個例子摘自 StackOverflow :
 
def foo[T](x: List[T])(implicit m: Manifest[T]) = {
if (m <:< manifest[String])
println("Hey, this list is full of strings")
else
println("Non-stringy list")
}
 
foo(List("one", "two")) // Hey, this list is full of strings
foo(List(1, 2)) // Non-stringy list
foo(List("one", 2)) // Non-stringy list
隱式參數m是由編譯器根據上下文自動傳入的,比如上面是編譯器根據 "one","two" 推斷出 T 的類型是 String,
從而隱式的傳入了一個Manifest[String]類型的對象參數,使得運行時可以根據這個參數做更多的事情。
 
不過上面的foo 方法定義使用隱式參數的方式,仍顯得囉嗦,於是scala裏又引入了“上下文綁定”,
回顧一下之前 context bounds,使得foo方法
 
def foo[T](x: List[T]) (implicit m: Manifest[T])
可以簡化爲:
 
def foo[T:Manifest] (x: List[T])
這個機制起因是scala2.8對數組的重新設計而引入的,原本只是爲了解決數組的問題(後續介紹數組類型),
後續被用在更多方面。在引入Manifest的時候,還引入了一個更弱一點的ClassManifest,
所謂的弱是指類型信息不如Manifest那麼完整,主要針對高階類型的情況:
 
scala> class A[T]
 
scala> val m = manifest[A[String]]
 
scala> val cm = classManifest[A[String]]
根據規範裏的說法,m的信息是完整的:m: Manifest[A[String]] = A[java.lang.String],
而 cm 則只有 A[_] 即不包含類型參數的信息,
但我在2.10下驗證cm也是:cm: ClassManifest[A[String]] = A[java.lang.String]
 
在獲取類型其類型參數時也是都包含的:
 
scala> m.typeArguments
res8: List[scala.reflect.Manifest[_]] = List(java.lang.String)
 
scala> cm.typeArguments
res9: List[scala.reflect.OptManifest[_]] = List(java.lang.String)
後來從這個帖子裏,看到一些案例,只在:
 
scala> class A[B] // 注意在2.10下與帖子中不一致,A[+B] 也是同樣的效果
defined class A
 
scala> manifest[A[_]]
res15: scala.reflect.Manifest[A[_]] = A[_ <: Any]
 
scala> classManifest[A[_]]
res16: scala.reflect.ClassTag[A[_]] = A[<?>]
到這裏我們基本明白了 Manifest 與 ClassManifest,
不過scala在2.10裏卻用TypeTag替代了Manifest,
用ClassTag替代了ClassManifest,
原因是在路徑依賴類型中,Manifest存在問題:
 
scala> class Foo{class Bar}
 
scala> def m(f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar]) = ev
 
scala> val f1 = new Foo;val b1 = new f1.Bar
scala> val f2 = new Foo;val b2 = new f2.Bar
 
scala> val ev1 = m(f1)(b1)
ev1: Manifest[f1.Bar] = [email protected]#Foo$Bar
 
scala> val ev2 = m(f2)(b2)
ev2: Manifest[f2.Bar] = [email protected]#Foo$Bar
 
scala> ev1 == ev2 // they should be different, thus the result is wrong
res28: Boolean = true
ev1 不應該等於 ev2 的,因爲其依賴路徑(外部實例)是不一樣的。
 
還有其他因素(見下面的引用),所以在2.10版本里,使用 TypeTag 替代了 Manifest
 
Manifests are a lie. It has no knowledge of variance (assumes all type parameters are co-variants),
and it has no support for path-dependent, existential or structural types.
 
TypeTags are types as the compiler understands them. Not “like” the compiler understands them,
but “as” the compiler understands them — the compiler itself use TypeTags. It’s not 1-to-1, it’s just 1. :-)
*/
}
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章