Scala入门到精通——第二十四节 高级类型 (三)
本節(jié)主要內(nèi)容
在Scala中,類(class)與類型(type)是兩個(gè)不一樣的概念。我們知道類是對(duì)同一類型數(shù)據(jù)的抽象,而類型則更具體。比如定義class List[T] {}, 可以有List[Int] 和 List[String]等具體類型,稱List為類,而List[Int]、List[String]則為類型。從這方面看:類型一致的對(duì)象它們的類也是一致的;而類一致的,其類型不一定一致。例如:
//List[Int]與List[String]它們的類是相同的即List scala> classOf[List[Int]] == classOf[List[String]] res1: Boolean = truescala> import scala.reflect.runtime.universe._ import scala.reflect.runtime.universe._ //類相同,但它們的類型是不一樣的 scala> typeOf[List[Int]] == typeOf[List[String]] res3: Boolean = false1. Type Specialization
Type Specialization,一般被翻譯成類型專門(mén)化,它主要是用來(lái)解決泛型的類型擦除和自動(dòng)裝箱拆箱的問(wèn)題。在Java語(yǔ)言當(dāng)中,泛型生成字節(jié)碼文件時(shí)會(huì)進(jìn)行泛型類型擦除,類型擦除后利用上界類型(一般是Object)來(lái)替代,但這么做的話有問(wèn)題,這是因?yàn)樵贘ava語(yǔ)言中基本類型與對(duì)象類型是不能相互引用的,java中的基本類型不能使用泛型。解決方案是利用對(duì)應(yīng)的對(duì)象類型來(lái)進(jìn)行替代,例如int對(duì)應(yīng)Integer類型,但這種方式并不能解決根本問(wèn)題。為方便后面Type Specialization的理解,我們先從java的類型擦除、自裝箱與拆箱講起。
1 類型擦除
假設(shè)我們利用Java泛型定義了下面的Person類:
- 22
經(jīng)過(guò)類型擦除后,最終變?yōu)?#xff1a;
public class Person {private Object firstName;private Object secondName;public Person(Object firstName,Object secondName){this.firstName=firstName;this.secondName=secondName;}public Object getFirstName() {return firstName;}public void setFirstName(Object firstName) {this.firstName = firstName;}public Object getSecondName() {return secondName;}public void setSecondName(Object secondName) {this.secondName = secondName;}}- 21
經(jīng)過(guò)類型擦除后的類稱為原始類型,從這點(diǎn)來(lái)看,java中的泛型其實(shí)是一個(gè)偽泛型,它只在編譯層次進(jìn)行實(shí)現(xiàn),在生成字碼碼這部分泛型信息被擦除。下面的例子證明也證明了這一點(diǎn):
public static void main(String[] args) {Person<String> p1=new Person<String>("張", "三");Person<Integer> p2=new Person<Integer>(1, 23);//下面的代碼返回的是trueSystem.out.println(p1.getClass()==p2.getClass());}java中的類型擦除會(huì)引起一些問(wèn)題,具體可以參考http://blog.csdn.net/lonelyroamer/article/details/7868820
2 自動(dòng)裝箱與拆箱
在前面給的示例代碼中,我們直接使用
Person<Integer> p2=new Person<Integer>(1, 23);
需要注意的是這里使用的是java的基本類型進(jìn)行對(duì)象的創(chuàng)建,而給定的具體類型是Integer,此時(shí)Java會(huì)幫我們自動(dòng)進(jìn)行轉(zhuǎn)換,這個(gè)轉(zhuǎn)換操作被稱為自動(dòng)裝箱(autoboxing),上面的代碼相當(dāng)于:Person<Integer> p2=new Person<Integer>(Integer.valueOf(1), Integer.valueOf(23));。
下面的代碼演示了拆箱(unboxing)
//Integer firstName =Integer.valueOf(23) Integer firstName = 23; //自動(dòng)裝箱 //拆箱,實(shí)際執(zhí)行 int name = firstName .intValue(); int name = firstName ;自動(dòng)裝箱與拆箱需要損耗一定的性能,當(dāng)性能要求較高時(shí)需要程序員手動(dòng)云進(jìn)行轉(zhuǎn)換。scala中的Type Specialization解決了這些問(wèn)題。它的語(yǔ)法很簡(jiǎn)單,通過(guò)注解進(jìn)行類型專門(mén)化聲明,如:
//在泛型參數(shù)前面加@specialized進(jìn)行Type Specialization abstract class List[@specialized T]{def apply(x:T)def map[S](f:T=>S) }上述代碼編譯后會(huì)生成下列字代碼文件,如下圖
從圖中可以看到,共生成了九個(gè)版本的List,其中這九個(gè)文件分別對(duì)應(yīng)scala中的九種基本類型即Unit, Boolean, Byte, Short, Char, Int, Long, Float, Double。感興趣的可以利用javap命令進(jìn)行查看,這里給出其Byte類型的實(shí)現(xiàn):
D:\ScalaWorkspace\ScalaChapter24\bin\cn\scala\xtwy\advancedtype>javap -private L ist$mcB$sp.class Compiled from "TypeSpecilization.scala" public abstract class cn.scala.xtwy.advancedtype.List$mcB$sp extends cn.scala.xt wy.advancedtype.List<java.lang.Object> {public abstract void apply(byte);public abstract <S extends java/lang/Object> void map(scala.Function1<java.lan g.Object, S>);public cn.scala.xtwy.advancedtype.List$mcB$sp(); }@specialized 還可以更細(xì)致,限定某個(gè)或幾個(gè)基本類型,例如:
abstract class List[@specialized T]{//指定生成Int類型的版本def apply[@specialized (Int) S](x:S):S指定生成Boolean及Double類型的版本def map[@specialized (Boolean,Double) S](f:T=>S) }- 1
在上一講中我們看到了Function1及Function2的類定義中也使用@specialize進(jìn)行注解,如:
@annotation.implicitNotFound(msg = "No implicit view available from ${T1} => ${R}.") trait Function1[@specialized(scala.Int, scala.Long,scala.Float, scala.Double/*, scala.AnyRef*/) -T1,scala.Float, scala.Long, scala.Double/*,scala.AnyRef*/) +R] extends AnyRef可以看到,Function1類也進(jìn)行了類型專門(mén)化。
2. Manifest、TypeTag、ClassTag
本節(jié)內(nèi)容大多來(lái)源于自官方文檔http://docs.scala-lang.org/overviews/reflection/typetags-manifests.html,大家在學(xué)習(xí)的時(shí)候,可看打開(kāi)API文檔,對(duì)本節(jié)內(nèi)容進(jìn)行理解。
由于類型擦除的影響,編譯期存在的類型信息在編譯后不存在了,在程序運(yùn)行時(shí)不能獲取該信息,但某些場(chǎng)景下可能需要得到編譯期的類型信息,scala能夠做到這一點(diǎn),它通過(guò)Manifest和TypeTag來(lái)保存類型信息并在運(yùn)行時(shí)使用該信息。那Manifest與TypeTag有什么區(qū)別呢?Manifest在scala.reflect包中,它在scala.reflect包中,而TypeTag 在scala.reflect.runtime.universe包中定義;TypeTag可以用來(lái)替代Manifest,功能更強(qiáng)大一點(diǎn),Manifest不能識(shí)別路徑依賴類型,例如對(duì)于class Outter{ class Inner},假設(shè)分別創(chuàng)建了兩個(gè)不同的外部類,outter.Inner, outter2.Inner, Manifest就會(huì)識(shí)別為同一類型,而TypeTag不會(huì),另外TypeTag可以使用typeOf[T] 來(lái)檢查類型參數(shù)。
下面的代碼給出了Manifest的用法:
object ManifestType extends App {def print1[T](x: List[T])(implicit m: Manifest[T]) = {if (m <:< manifest[String])println("字符串類型的List")elseprintln("非字符串類型的List")}print1(List("one", "two")) print1(List(1, 2)) print1(List("one", 2)) }- 1
隱式參數(shù)m由編譯器根據(jù)上下文自動(dòng)傳入,例如print1(List(“one”, “two”)) ,編譯器會(huì)根據(jù)”one”,”two” 實(shí)際類型推斷出 T 的類型是 String,再隱式地傳入了Manifest[String]類型的對(duì)象參數(shù),使得運(yùn)行時(shí)可以根據(jù)這個(gè)參數(shù)做更多的事情。
下面的代碼演示了如何使用TypeTag
import scala.reflect.runtime.universe._def getTypeTag[T: TypeTag](a: T) = typeTag[T]//下列語(yǔ)句返回TypeTag[List[Int]]println(getTypeTag(List(1, 2, 3)))從上面的代碼可以看到,typeTag返回的是具體的類型,而不是類型擦除之后的類型any,即TypeTag保存所有具體的類型。在運(yùn)行時(shí)可以通過(guò)模式匹配來(lái)精確地對(duì)類型進(jìn)行判斷:
import scala.reflect.runtime.universe._def patternMatch[A : TypeTag](xs: List[A]) = typeOf[A] match { //利用類型約束進(jìn)行精確匹配case t if t =:= typeOf[String] => "list of strings" case t if t <:< typeOf[Int] => "list of ints"}println(patterMatch(List(1,2)))上邊的typeOf[A]在傳入?yún)?shù)為L(zhǎng)ist(“String”)時(shí),得到結(jié)果是java.lang.String。typeOf[A]接受一個(gè)類型為T(mén)ypeTag[a]的隱式參數(shù),編譯器生成的TypeTag隱式參數(shù)會(huì)被傳給typeOf[A] 。 有4種TypeTag:
1 scala.reflect.api.TypeTags#TypeTag. A full type descriptor of a Scala type. For example, a TypeTag[List[String]] contains all type information, in this case, of typescala.List[String].
2 scala.reflect.ClassTag. A partial type descriptor of a Scala type. For example, a ClassTag[List[String]] contains only the erased class type information, in this case, of type
3 scala.collection.immutable.List.ClassTags provide access only to the runtime class of a type. Analogous to scala.reflect.ClassManifest.
4 scala.reflect.api.TypeTags#WeakTypeTag. A type descriptor for abstract types (see corresponding subsection below).
這給出最常用的ClassTag的用法:ClassTag[T]保存了被泛型擦除后的原始類型T,提供給運(yùn)行時(shí)程序使用。
scala> import scala.reflect.runtime.universe._ import scala.reflect.runtime.universe._scala> val tt = typeTag[Int] tt: reflect.runtime.universe.TypeTag[Int] = TypeTag[Int]scala>scala> import scala.reflect._ import scala.reflect._//得到具體類型 scala> val ct = classTag[String] ct: scala.reflect.ClassTag[String] = java.lang.String3. Scala類型系統(tǒng)總結(jié)
到此,Scala的類型系統(tǒng)基本介紹完畢,下表給出了Scala中常見(jiàn)的類型
| 類 | class Person |
| 特質(zhì) | trait Closable |
| 元組類型 | (T1,T2,T3,…) |
| 函數(shù)類型 | (T1,T2,t3,…)=>T |
| 參數(shù)類型(泛型) | class Person[T1,T2,…] |
| 單例類型 | this.type |
| 類型投影 | Outter#Inner |
| 復(fù)合類型 | A with B with C… |
| 結(jié)構(gòu)體類型 | {def f():Unit ….} |
| 中置類型 | T1 A T2 |
| 存在類型 | T forSome {} |
總結(jié)
以上是生活随笔為你收集整理的Scala入门到精通——第二十四节 高级类型 (三)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Scala入门到精通——第二十三节 高级
- 下一篇: Scala入门到精通——第二十五节 提取