1. 程式人生 > >Scala入門到精通——第二十四節 高級類型 (三)

Scala入門到精通——第二十四節 高級類型 (三)

func double tor 結構體 入門到精通 strac 命令 person 字節碼

作者:擺擺少年夢
視頻地址:http://blog.csdn.net/wsscy2004/article/details/38440247

本節主要內容

  1. Type Specialization
  2. Manifest、TypeTag、ClassTag
  3. Scala類型系統總結

在scala中,類(class)與類型(type)是兩個不一樣的概念。我們知道類是對同一類型數據的抽象,而類型則更詳細。

比方定義class List[T] {}, 能夠有List[Int] 和 List[String]等詳細類型。稱List為類,而List[Int]、List[String]則為類型。

從這方面看:類型一致的對象它們的類也是一致的。而類一致的,其類型不一定一致。比如:

//List[Int]與List[String]它們的類是同樣的即List
scala> classOf[List[Int]] == classOf[List[String]]
res1: Boolean = true

scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
//類同樣,但它們的類型是不一樣的
scala>  typeOf[List[Int]] == typeOf[List[String]]
res3: Boolean = false

1. Type Specialization

Type Specialization。一般被翻譯成類型專門化,它主要是用來解決泛型的類型擦除和自己主動裝箱拆箱的問題。在JAVA語言其中。泛型生成字節碼文件時會進行泛型類型擦除,類型擦除後利用上界類型(通常是Object)來替代。但這麽做的話有問題。這是由於在Java語言中基本類型與對象類型是不能相互引用的,java中的基本類型不能使用泛型。解決方式是利用相應的對象類型來進行替代,比如int相應Integer類型,但這樣的方式並不能解決根本問題。為方便後面Type Specialization的理解,我們先從java的類型擦除、自裝箱與拆箱講起。

1 類型擦除
如果我們利用Java泛型定義了以下的Person類:

//Java泛型類
public class Person<T> {
    private T firstName;
    private T secondName;
    public Person(T firstName,T secondName){
        this.firstName=firstName;
        this.secondName=secondName;
    }
    public T getFirstName() {
        return firstName;
    }
    public void setFirstName(T firstName) {
        this.firstName = firstName;
    }
    public T getSecondName() {
        return secondName;
    }
    public void setSecondName(T secondName) {
        this.secondName = secondName;
    }

}

經過類型擦除後,終於變為:

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;
    }

}

經過類型擦除後的類稱為原始類型,從這點來看,java中的泛型事實上是一個偽泛型,它僅僅在編譯層次進行實現。在生成字碼碼這部分泛型信息被擦除。

以下的樣例證明也證明了這一點:

public static void main(String[] args) {
        Person<String> p1=new Person<String>("張", "三");
        Person<Integer> p2=new Person<Integer>(1, 23);
        //以下的代碼返回的是true
        System.out.println(p1.getClass()==p2.getClass());
    }

java中的類型擦除會引起一些問題,詳細能夠參考http://blog.csdn.net/lonelyroamer/article/details/7868820

2 自己主動裝箱與拆箱

在前面給的演示樣例代碼中。我們直接使用
Person<Integer> p2=new Person<Integer>(1, 23);
須要註意的是這裏使用的是java的基本類型進行對象的創建。而給定的詳細類型是Integer,此時Java會幫我們自己主動進行轉換,這個轉換操作被稱為自己主動裝箱(autoboxing),上面的代碼相當於:Person<Integer> p2=new Person<Integer>(Integer.valueOf(1), Integer.valueOf(23));

以下的代碼演示了拆箱(unboxing)


//Integer firstName =Integer.valueOf(23) 
Integer firstName = 23; //自己主動裝箱
//拆箱,實際執行 int name  = firstName .intValue();
int name = firstName ; 

自己主動裝箱與拆箱須要損耗一定的性能。當性能要求較高時須要程序猿手動雲進行轉換。Scala中的Type Specialization攻克了這些問題。它的語法非常easy,通過註解進行類型專門化聲明。如:

[email protected] Specialization
abstract class List[@specialized T]{
  def apply(x:T)
  def map[S](f:T=>S)
}

上述代碼編譯後會生成下列字代碼文件。例如以下圖
技術分享

從圖中能夠看到。共生成了九個版本號的List,其中這九個文件分別相應scala中的九種基本類型即Unit, Boolean, Byte, Short, Char, Int, Long, Float, Double。

感興趣的能夠利用javap命令進行查看,這裏給出其Byte類型的實現:

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 還能夠更仔細。限定某個或幾個基本類型,比如:

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)
}

在上一講中我們看到了Function1及Function2的類定義中[email protected]

@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類也進行了類型專門化。

2. Manifest、TypeTag、ClassTag

本節內容大多來源於自官方文檔http://docs.scala-lang.org/overviews/reflection/typetags-manifests.html,大家在學習的時候。可看打開API文檔,對本節內容進行理解。

由於類型擦除的影響。編譯期存在的類型信息在編譯後不存在了,在程序執行時不能獲取該信息。但某些場景下可能須要得到編譯期的類型信息。scala能夠做到這一點,它通過Manifest和TypeTag來保存類型信息並在執行時使用該信息。那Manifest與TypeTag有什麽差別呢?Manifest在scala.reflect包中,它在scala.reflect包中。而TypeTag 在scala.reflect.runtime.universe包中定義;TypeTag能夠用來替代Manifest。功能更強大一點。Manifest不能識別路徑依賴類型,比如對於class Outter{ class Inner}。如果分別創建了兩個不同的外部類,outter.Inner, outter2.Inner, Manifest就會識別為同一類型,而TypeTag不會。另外TypeTag能夠使用typeOf[T] 來檢查類型參數。

以下的代碼給出了Manifest的使用方法:

object ManifestType extends App {
  def print1[T](x: List[T])(implicit m: Manifest[T]) = {
    if (m <:< manifest[String])
      println("字符串類型的List")
    else
      println("非字符串類型的List")
  }
  print1(List("one", "two")) 
  print1(List(1, 2)) 
  print1(List("one", 2))
}

隱式參數m由編譯器依據上下文自己主動傳入,比如print1(List(“one”, “two”)) ,編譯器會依據”one”,”two” 實際類型判斷出 T 的類型是 String,再隱式地傳入了Manifest[String]類型的對象參數。使得執行時能夠依據這個參數做很多其它的事情。

以下的代碼演示了怎樣使用TypeTag

import scala.reflect.runtime.universe._
  def getTypeTag[T: TypeTag](a: T) = typeTag[T]
  //下列語句返回TypeTag[List[Int]]
  println(getTypeTag(List(1, 2, 3)))

從上面的代碼能夠看到,typeTag返回的是詳細的類型,而不是類型擦除之後的類型any,即TypeTag保存全部詳細的類型。在執行時能夠通過模式匹配來精確地對類型進行判斷:

import scala.reflect.runtime.universe._
  def patternMatch[A : TypeTag](xs: List[A]) = typeOf[A] match { 
    //利用類型約束進行精確匹配
    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]在傳入參數為List(“String”)時,得到結果是java.lang.String。

typeOf[A]接受一個類型為TypeTag[a]的隱式參數,編譯器生成的TypeTag隱式參數會被傳給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,提供給執行時程序使用。

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.String

3. Scala類型系統總結

到此,Scala的類型系統基本介紹完成。下表給出了Scala中常見的類型

類型 語法類型
class Person
特質 trait Closable
元組類型 (T1,T2,T3,…)
函數類型 (T1,T2,t3,…)=>T
參數類型(泛型) class Person[T1,T2,…]
單例類型 this.type
類型投影 Outter#Inner
復合類型 A with B with C…
結構體類型 {def f():Unit ….}
中置類型 T1 A T2
存在類型 T forSome {}

加入公眾微信號,能夠了解很多其它最新Spark、Scala相關技術資訊
技術分享

Scala入門到精通——第二十四節 高級類型 (三)