1. 程式人生 > >【圖文詳細 】Scala——泛型

【圖文詳細 】Scala——泛型

3、Scala 泛型 

 

3.1、Scala 泛型基礎 

泛型用於指定方法或類可以接受任意型別引數,引數在實際使用時才被確定,泛型可以有效 地增強程式的適用性,使用泛型可以使得類或方法具有更強的通用性。泛型的典型應用場景 是集合及集合中的方法引數,可以說同 Java 一樣,Scala 中泛型無處不在,具體可檢視 Scala 的 API 
 
泛型類:指定類可以接受任意型別引數。

泛型方法:指定方法可以接受任意型別引數。 

package com.mazh.scala.day3 
 
class Person[T](var name:T) 
class Student[T,S](name:T,var age:S) extends Person(name) 
 
/** 
  * 作者: 李濤  https://blog.csdn.net/qq_42246689 
  */ 
object GenericTypeTest { 
  def main(args: Array[String]): Unit = { 
    println (new Student[String,Int]("黃渤",33).name) 
  } 
} 
 

 

3.2、Scala 型別變數界定 

型別變數界定是指在泛型的基礎上,對泛型的範圍進行進一步的界定,從而縮小泛型的具體

範圍 比如下面的程式碼編譯不通過:

class GenericTypeTest2 { 
  def compare[T](first:T,second:T) = { 
    if (first.compareTo(second)>0) 
      first 
    else 
      second 
  } 
} 
 
object GenericTypeTest2{ 
  def main(args: Array[String]): Unit = { 
    val tvb = new GenericTypeTest2 
    println (tvb.compare("A", "B")) 
  } 
} 

程式碼為什麼編譯不通過,是因為:泛型 T 並不一定具備 compareTo 方法 
 

如果想編譯通過,請做如下更改: 

class GenericTypeTest2 { 
  def compare[T <: Comparable[T]](first:T,second:T)={ 
    if (first.compareTo(second)>0) 
      first 
    else 
      second 
  } 
} 
 
object GenericTypeTest2{ 
  def main(args: Array[String]): Unit = { 
    val tvb=new GenericTypeTest2 
    println (tvb.compare("A", "B")) 
 } 
} 

程式碼中改動的地方:

T <: Comparable[T]

這是什麼意思呢?

compareTo 方法中如果輸入的型別處於 Comparable 類對應繼承層次結構中,則是合法的, 否則的話編譯會報錯 

 

3.3、Scala 檢視界定

上面講的型別變數界定建立在類繼承層次結構的基礎上,但有時候這種限定不能滿足實際要 求,如果希望跨越類繼承層次結構時,可以使用檢視界定來實現的,其後面的原理是通過隱 式轉換來實現。 
 
隱含引數和方法也可以定義隱式轉換,稱作檢視。檢視的繫結從另一個角度看就是 implicit 的轉換。主要用在兩個場合: 、當一個 T 型別的變數 t 要裝換成 A 型別時 、當一個型別 T 的變數 t 無法擁有 A 型別的 a 方法或變數時  其實檢視的繫結是為了更方便的使用隱式裝換 檢視界定利用<%符號來實現 
 
如下:程式碼的執行會丟擲異常

object GenericTypeTest3{ 
 
  def main(args: Array[String]): Unit = { 
    // 這是合法的語句 
    val s= Student11 ("john","170") 
    // 下面這條語句不合法,這是因為 ,Int 型別沒有實現 Comparable 介面 
    val s2= Student11 ("john",170) 
  } 
} 

如果想通過,那麼要使用檢視界定的技術 更改之後的程式碼: 

package com.mazh.scala.day3 
 
case class Student11[T,S <% Comparable[S]](var name:T,var height:S) 
object GenericTypeTest3{ 
 
  def main(args: Array[String]): Unit = { 
    // 這是合法的語句 
    val s= Student11 ("john","170") 
    // Int 型別 的變數經過 隱式 轉換成了 R ichInt 型別, R ichInt 型別是實現了 C omparable 介面的 
    val s2= Student11 ("john",170) 
  } 
} 

改動的程式碼:

case class Student11[T, S <% Comparable[S]](var name:T, var height:S) 
 
能執行的原因:

利用<%符號對泛型 S 進行限定,它的意思是 S 可以是 Comparable 類繼承層次結構中實現了 Comparable 介面的類,也可以是能夠經過隱式轉換得到的實現了 Comparable 介面的類。 
 
上面改動的語句在檢視界定中是合法的,因為 Int 型別此時會隱式轉換為 RichInt 類,而 RichInt 類屬於 Comparable 繼承層次結構。Int 類會隱式轉換成 RichInt 類,RichInt 並不是直 接實現 Comparable 口,而是通過 ScalaNumberProxy 類將 Comparable 中的方法繼承過來。 

 

3.4、Scala 上界下界 

上界、下界介紹  在指定泛型型別時,有時需要界定泛型型別的範圍,而不是接收任意型別。比如,要求某個 泛型型別,必須是某個類的子類,這樣在程式中就可以放心的呼叫父類的方法,程式才能正 常的使用與執行。此時,就可以使用上下邊界 Bounds 的特性;  Scala 的上下邊界特性允許泛型型別是某個類的子類,或者是某個類的父類; 
1、U >: T 這是型別下界的定義,也就是 U 必須是型別 T 的父類(或本身,自己也可以認為是自己的父 類)。 
2、S <: T 這是型別上界的定義,也就是 S 必須是型別 T 的子類(或本身,自己也可以認為是自己的子 類)。 

3.4.1、上界 
在講解型別變數繫結的內容中,咱們寫過這麼一段程式碼:

class GenericTypeTest2 { 
  def compare[T <: Comparable[T]](first:T,second:T)={ 
    if (first.compareTo(second)>0) 
      first 
    else 
      second 
  } }

標紅的地方,其實就是上界,因為它限定了繼承層次結構中最頂層的類,例如 T <: Comparable[T] 表示泛型 T 的型別的最頂層類是 Comparable,所有輸入是 Comparable 的子類都是合法的,其 它的都是非法的,因為被稱為上界 

 

3.4.2、下界 

當然,除了上界之外,還有個非常重要的內容就是下界,下界通過>:符號來標識,比如: 

class A 
class B extends A 
class C extends B 
class D extends C 

如果程式碼中這麼寫:opt[T >: C]

那麼表示 T 的型別只能是 A,B,C 了。不能是 D,其實就是限制了最底層的型別是什麼。在類 的繼承結構體系中,從上到下,只能到型別 C 為止 
 
下界的作用主要是保證型別安全 
 
例項程式碼:

object GenericTypeTest4 { 
 
  def getIDCard[R >: Son](person:R): Unit ={
     println("好吧,他的身份證就交給你保管了"); 
  } 
 
  def main(args: Array[String]): Unit = { 
 
// getIDCard [ T ] (t:T) 前面 這個 T 表示方法中 的引數 型別 被固定下來。 
// 在 定義 的時候還不知道這個 T 型別 到底應該是什麼,但是 呼叫 的時候,被確定 下來 是某種型別 
// 但是 , 引數 中的型別,無論如何都可以是 T 的子類 型別,這屬於多型範疇
 
    getIDCard[GranderFather](new GranderFather) 
    getIDCard[GranderFather](new Father) 
    getIDCard[Father](new Father) 
    getIDCard[Son](new Son) 
 
    // 這句程式碼會報錯
     getIDCard[Tongzhuo](new Tongzhuo) 
  } 
} 
 
class GranderFather 
class Father extends GranderFather 
class Son extends Father 
class Tongzhuo

 

3.5、Scala 逆變和協變 


3.5.1、協變 
協變定義形式如:

trait List[+T]{}

當型別 B 是型別 A 的子型別時,則 List[B]也可以認為是 List[A}的子型別,即 List[B]可以泛化 為 List[A]。

也就是被引數化型別的泛化方向與引數型別的方向是一致的,所以稱為協變 (covariance) 

首先,Java 中不存在協變: 

public class TypeTest { 
  
 public static void main(String[] args) { 
   
  java.util.List<String> s1 = new LinkedList<String>(); 
  java.util.List<Object> s2 = new LinkedList<Object>(); 
  /**
   * 下面這條語句會報錯 
   * Type mismatch: cannot convert from List<String> to List<Object> 
   */ 
  s2 = s1; 
 } 
} 

然在類層次結構上看,String 是 Object 類的子類,但 List<String>並不是的 List<Object>子類, 也就是說它不是協變的。Java 的靈活性就這麼差嗎?其實 Java 不提供協變和逆變這種特性 是有其道理的,這是因為協變和逆變會破壞型別安全。假設上面的程式碼是合法的,我們此時 完全可以 s2.add(new Person(“xuzheng”)往集合中新增 Person 物件,但此時我們知道,s2 已 經指向了 s1,而 s1 裡面的元素型別是 String 型別,這時其型別安全就被破壞了,從這個角 度來看,Java 不提供協變和逆變是有其合理性的。 
 
那為什麼 Java 不支援,而 Scala 支援呢? 
 
先來看一段 Scala 程式碼:Scala 比 Java 更靈活,當不指定逆變和協變時,和 Java 是一樣的。 程式碼如下:
 

package com.mazh.scala.day3.gao 
 
object XieBianTest { 
  def main(args: Array[String]): Unit = {
     val list1:MyList[String]= new MyList[String]("黃",null)
     val list2:MyList[String]= new MyList[String]("黃",new MyList[String]("黃",null)) 
  } 
} 
class MyList[T](val head: T, val tail: MyList[T]){} 

3.5.2、逆變 
逆變定義形式如:trait List[-T]{} 當型別 B 是型別 A 的子型別,

則 Queue[A]反過來可以認為是 Queue[B}的子型別。

也就是被 引數化型別的泛化方向與引數型別的方向是相反的,所以稱為逆變(contravariance)