1. 程式人生 > >一文詳解scala泛型及型別限定

一文詳解scala泛型及型別限定

640?wx_fmt=png

今天知識星球球友,微信問浪尖了一個spark原始碼閱讀中的型別限定問題。這個在spark原始碼很多處出現,所以今天浪尖就整理一下scala型別限定的內容。希望對大家有幫助。

640?wx_fmt=png640?wx_fmt=pngscala型別引數要點

1. 非變

trait Queue[T] {} 
這是非變情況。這種情況下,當型別S是型別A的子型別,則Queue[S]不可認為是Queue[A]的子型別或父型別,這種情況是和Java一樣的。 
2. 協變
trait Queue[+T] {} 
這是協變情況。這種情況下,當型別S是型別A的子型別,則Queue[S]也可以認為是Queue[A}的子型別,即Queue[S]可以泛化為Queue[A]。也就是被引數化型別的泛化方向與引數型別的方向是一致的,所以稱為協變。 
3. 逆變


trait Queue[-T] {} 
這是逆變情況。這種情況下,當型別S是型別A的子型別,則Queue[A]反過來可以認為是Queue[S}的子型別。也就是被引數化型別的泛化方向與引數型別的方向是相反的,所以稱為逆變。 

4. 型別下界

U >: T

這是型別下界的定義,也就是U必須是型別T的父類(或本身,自己也可以認為是自己的父類)。

5. 型別上屆

S <: T

這是型別上界的定義,也就是S必須是型別T的子類(或本身,自己也可以認為是自己的子類)。

640?wx_fmt=png泛型與約束實戰

1 泛型函式

   ClassTag[T]儲存了泛型擦除後的原始型別T,提供給被執行時的。

/*
    *  泛型[],中括號F、S、T都表示執行時引數型別,
    * ClassTag[T]儲存了泛型擦除後的原始型別T,提供給被執行時的。
    */

   class Triple[F: ClassTag, S, T](val first: F, val second: S, val third: T)
   
   object HelloTypeParam {
     def main(args: Array[String]): Unit = {
   
       // 執行執行程式碼:val triple: Triple[String, Int, Double]
       val triple = new Triple("Spark", 3, 3.1415)
       
       // 執行執行程式碼:val bigData: Triple[String, String, Char]

       val bigData = new Triple[String, String, Char]("Spark", "Hadoop", 'R');
       
       // getData函式傳入泛型為T的執行時List型別引數,返回list.length / 2的整數。
       def getData[T](list:List[T]) = list(list.length / 2)
       // List索引從0開始,執行結果:Hadoop
       println(getData(List("Spark","Hadoop",'R')));
       
       // 獲得getData函式引用
       val f = getData[Int] _
       // 呼叫getData函式,執行結果:4
       println(f(List(1,2,3,4,5,6)));
       
     }
   }

2 型別變數界定

泛型引數型別限定,限定具體類的可以呼叫特定的方法。

/*
    * <:泛型型別限定符,表示只限定Comparable子類
    * Comparable[T]:為T下界,T:為Comparable[T]上界
    */

   class Pair[T <: Comparable[T]](val first: T, val second: T) {
     // compareTo方法進行比較,如果大於0返回first
     def bigger = if (first.compareTo(second) > 0) first else second
   }
   
   
   // 宣告帶T泛型引數的類
   class Pair_Lower_Bound[T](val first: T, val second: T) {
       // 傳入的引數泛型T 必須為 R的父類(超類),返回構造Pair_Lower_Bound物件
       // R:為T的上界,T:為R下界  
       def replaceFirst[R>:T](newFirst:R) = new Pair_Lower_Bound[R](newFirst,second)
   }
   
   object TypeVariablesBounds {
     def main(args: Array[String]): Unit = {
       // 函式呼叫
       var pair = new Pair("Spark", "Hadoop")
       // 執行結果:Spark
       println(pair.bigger)
     }
   }

3 泛型檢視限定

泛型檢視限定:表示把傳入不是Comparable[T]型別的隱式傳換為Comparable[T]型別,Comparable[T]:為T下界,T:為Comparable[T]上界。

/*
    * <%泛型檢視限定符,表示把傳入不是Comparable[T]型別的 隱式傳換 為Comparable[T]型別
    * Comparable[T]:為T下界,T:為Comparable[T]上界
    */

   class PairNotPerfect[T <% Comparable[T]](val first: T, val second: T) {
     // compareTo方法進行比較,如果大於0返回first
     def bigger = if (first.compareTo(second) > 0) first else second
   }
   
   /*
    * <%泛型檢視限定符,表示把傳入不是Ordered[T]型別的 隱式傳換 為Ordered[T]型別
    * Ordered[T]:為T下界,T:為Ordered[T]上界
    * Ordered繼承: extends Any with java.lang.Comparable[A]
    */

   class PairBetter[T <% Ordered[T]](val first: T, val second: T) {
     def bigger = if (first.compareTo(second) > 0) first else second
   }
   
   object ViewVariablesBounds {
     def main(args: Array[String]): Unit = {
       // 函式呼叫
       var pair = new PairNotPerfect("Spark", "Hadoop");
       // 執行結果:Spark
       println(pair.bigger)
       
       // 函式呼叫,Int型別進行隱式轉換,將Int -> RichInt,RichInt實現了Comparable介面
       var pairInt = new PairNotPerfect(3,5)
       // 執行結果:5
       println(pairInt.bigger);
       
       // 函式呼叫,Int型別進行隱式轉換,將String -> RichString,RichString實現了Comparable介面
       var pairBetterStr = new PairBetter("Java","Scala");
       println(pairBetterStr.bigger);
       
       // 函式呼叫
       var pairBetterInt = new PairBetter(20, 12);
       // 執行結果:Spark
       println(pairBetterInt.bigger)
     }
   }

4 上下文界定

上下文界定:上下文界定是隱式引數的語法糖。如:Ordering:可以進行隱式轉化的T型別。

class PairOrdering[T: Ordering](val first: T, val second: T) {
     // compareTo方法進行比較,如果大於0返回first
     def bigger(implicit ordered: Ordering[T]) = if (ordered.compare(first, second) > 0) first else second
   }
   
   object ContextBounds {
     def main(args: Array[String]): Unit = {
       // 函式呼叫
       var pair = new PairOrdering("Spark", "Hadoop")
       // 執行結果:Spark
       println(pair.bigger)
     }
   }

5 Manifest關鍵字

Manifest關鍵字:陣列在宣告時必須要求指定具體的型別,在函式泛型是無法知道具體型別,通過Manifest關鍵字使得執行時可以根據這個Manifest引數做更多的事情。

def main(args: Array[String]): Unit = {
   /*
    *  定義方法array
    *  Manifest:需要執行時儲存T的實際型別,執行時是做為引數執行在方法的上下文中。
    *  陣列在定義時必須知道具體的型別,所以在宣告方法時,需要新增Manifest
    */

   def arrayMake[T: Manifest](first: T, second: T) = {
     val r = new Array[T](2);
     r(0) = first;
     r(1) = second;
     // 返回r
     r;
   }

   /*
    *  執行結果:
    *  1
    *  2
    */

   arrayMake(1,2).foreach(println)
 }

6 ClassTag關鍵字

ClassTag[T]儲存了泛型擦除後的原始型別T,提供給被執行時的。

def main(args: Array[String]): Unit = { 
   
       /*
        * ClassTag:在執行時指定,在編譯時無法確定的
        */

       def mkArray[T:ClassTag](elems:T*) = Array[T](elems:_*)
       
       /*
        *  執行結果:
        *  42
        *  13
        */

       mkArray(42,13).foreach(println)
       
       /*
        * 執行結果:
        * Japan
        * Brazil
        * Germany
        */

       mkArray("Japan","Brazil","Germany").foreach(println)
   }

7 ClassManifest關鍵字

在引入Manifest的時候,還引入了一個更弱一點的ClassManifest,所謂的弱是指型別資訊不如Manifest那麼完整。用TypeTag替代了Manifest,用ClassTag替代了ClassManifest,原因是在路徑依賴型別中,Manifest存在問題。

class A[T]
   val m = manifest[A[String]]
   // 執行結果:com.scala.type_param.Manifest_ClassTag$A$1[java.lang.String]
   println(m);
   val cm = classManifest[A[String]]
   // 執行結果:com.scala.type_param.Manifest_ClassTag$A$1[java.lang.String]
   println(cm);

8 多重界定符

/*
     // 表示:A和B為T上界
     T <: A with B
     
     // 表示:A和B為T下界
     T >: A with B
     
     // 表示:同時擁有上界和下界,並且A為下界,B為上界,A為B的子類,順序不能顛倒。
     T >: A <: B
     
     // 表示:型別變數界定,即同時滿足AT這種隱式值和BT這種隱式值
     T:A:B
     
     // 表示:檢視界定,即同時能夠滿足隱式轉換的A和隱式轉換的B
     T <% A <% B
   */

9 Scala型別約束

def main(args: Array[String]): Unit = {
     // A =:=B // 表示A型別等同於B型別
     // A <:<B  // 表示A型別是B型別的子類
     def rocky[T](i: T)(implicit ev: T <:< java.io.Serializable) {
       // 執行結果:Life is short ,you need spark!!!
       println("Life is short ,you need spark!!!")
     }
     rocky("Spark")
   }

更多scala,spark,大資料知識,希望擴充套件視野解決疑難雜症及困惑,歡迎點選閱讀原文,加入浪尖知識星球。

本文整理自網路,若有侵權,請聯絡微信158570986刪除。

推薦閱讀:

640?wx_fmt=png