Scala 支持類型參數化,使得咱們可以編寫泛型程序。java
Java 中使用 <>
符號來包含定義的類型參數,Scala 則使用 []
。git
class Pair[T, S](val first: T, val second: S) { override def toString: String = first + ":" + second }
object ScalaApp extends App { // 使用時候你直接指定參數類型,也能夠不指定,由程序自動推斷 val pair01 = new Pair("heibai01", 22) val pair02 = new Pair[String,Int]("heibai02", 33) println(pair01) println(pair02) }
函數和方法也支持類型參數。github
object Utils { def getHalf[T](a: Array[T]): Int = a.length / 2 }
Scala 和 Java 同樣,對於對象之間進行大小比較,要求被比較的對象實現 java.lang.Comparable
接口。因此若是想對泛型進行比較,須要限定類型上界爲 java.lang.Comparable
,語法爲 S <: T
,表明類型 S 是類型 T 的子類或其自己。示例以下:編程
// 使用 <: 符號,限定 T 必須是 Comparable[T]的子類型 class Pair[T <: Comparable[T]](val first: T, val second: T) { // 返回較小的值 def smaller: T = if (first.compareTo(second) < 0) first else second }
// 測試代碼 val pair = new Pair("abc", "abcd") println(pair.smaller) // 輸出 abc
擴展:若是你想要在 Java 中實現類型變量限定,須要使用關鍵字 extends 來實現,等價的 Java 代碼以下:數組
public class Pair<T extends Comparable<T>> { private T first; private T second; Pair(T first, T second) { this.first = first; this.second = second; } public T smaller() { return first.compareTo(second) < 0 ? first : second; } }
在上面的例子中,若是你使用 Int 類型或者 Double 等類型進行測試,點擊運行後,你會發現程序根本沒法經過編譯:app
val pair1 = new Pair(10, 12) val pair2 = new Pair(10.0, 12.0)
之因此出現這樣的問題,是由於 Scala 中的 Int 類並無實現 Comparable 接口。在 Scala 中直接繼承 Comparable 接口的是特質 Ordered,它在繼承 compareTo 方法的基礎上,額外定義了關係符方法,源碼以下:ide
// 除了 compareTo 方法外,還提供了額外的關係符方法 trait Ordered[A] extends Any with java.lang.Comparable[A] { def compare(that: A): Int def < (that: A): Boolean = (this compare that) < 0 def > (that: A): Boolean = (this compare that) > 0 def <= (that: A): Boolean = (this compare that) <= 0 def >= (that: A): Boolean = (this compare that) >= 0 def compareTo(that: A): Int = compare(that) }
之因此在平常的編程中之因此你可以執行 3>2
這樣的判斷操做,是由於程序執行了定義在 Predef
中的隱式轉換方法 intWrapper(x: Int)
,將 Int 類型轉換爲 RichInt 類型,而 RichInt 間接混入了 Ordered 特質,因此可以進行比較。函數
// Predef.scala @inline implicit def intWrapper(x: Int) = new runtime.RichInt(x)
要想解決傳入數值沒法進行比較的問題,可使用視圖界定。語法爲 T <% U
,表明 T 可以經過隱式轉換轉爲 U,即容許 Int 型參數在沒法進行比較的時候轉換爲 RichInt 類型。示例以下:測試
// 視圖界定符號 <% class Pair[T <% Comparable[T]](val first: T, val second: T) { // 返回較小的值 def smaller: T = if (first.compareTo(second) < 0) first else second }
注:因爲直接繼承 Java 中 Comparable 接口的是特質 Ordered,因此以下的視圖界定和上面是等效的:大數據
// 隱式轉換爲 Ordered[T] class Pair[T <% Ordered[T]](val first: T, val second: T) { def smaller: T = if (first.compareTo(second) < 0) first else second }
若是你用的 Scala 是 2.11+,會發現視圖界定已被標識爲廢棄。官方推薦使用類型約束 (type constraint) 來實現一樣的功能,其本質是使用隱式參數進行隱式轉換,示例以下:
// 1.使用隱式參數隱式轉換爲 Comparable[T] class Pair[T](val first: T, val second: T)(implicit ev: T => Comparable[T]) def smaller: T = if (first.compareTo(second) < 0) first else second } // 2.因爲直接繼承 Java 中 Comparable 接口的是特質 Ordered,因此也能夠隱式轉換爲 Ordered[T] class Pair[T](val first: T, val second: T)(implicit ev: T => Ordered[T]) { def smaller: T = if (first.compareTo(second) < 0) first else second }
固然,隱式參數轉換也能夠運用在具體的方法上:
object PairUtils{ def smaller[T](a: T, b: T)(implicit order: T => Ordered[T]) = if (a < b) a else b }
上下文界定的形式爲 T:M
,其中 M 是一個泛型,它要求必須存在一個類型爲 M[T]的隱式值,當你聲明一個帶隱式參數的方法時,須要定義一個隱式默認值。因此上面的程序也可使用上下文界定進行改寫:
class Pair[T](val first: T, val second: T) { // 請注意 這個地方用的是 Ordering[T],而上面視圖界定和類型約束,用的是 Ordered[T],二者的區別會在後文給出解釋 def smaller(implicit ord: Ordering[T]): T = if (ord.compare(first, second) < 0) first else second } // 測試 val pair= new Pair(88, 66) println(pair.smaller) //輸出:66
在上面的示例中,咱們無需手動添加隱式默認值就能夠完成轉換,這是由於 Scala 自動引入了 Ordering[Int]這個隱式值。爲了更好的說明上下文界定,下面給出一個自定義類型的比較示例:
// 1.定義一我的員類 class Person(val name: String, val age: Int) { override def toString: String = name + ":" + age } // 2.繼承 Ordering[T],實現自定義比較器,按照本身的規則重寫比較方法 class PersonOrdering extends Ordering[Person] { override def compare(x: Person, y: Person): Int = if (x.age > y.age) 1 else -1 } class Pair[T](val first: T, val second: T) { def smaller(implicit ord: Ordering[T]): T = if (ord.compare(first, second) < 0) first else second } object ScalaApp extends App { val pair = new Pair(new Person("hei", 88), new Person("bai", 66)) // 3.定義隱式默認值,若是不定義,則下一行代碼沒法經過編譯 implicit val ImpPersonOrdering = new PersonOrdering println(pair.smaller) //輸出: bai:66 }
這裏先看一個例子:下面這段代碼,沒有任何語法錯誤,可是在運行時會拋出異常:Error: cannot find class tag for element type T
, 這是因爲 Scala 和 Java 同樣,都存在類型擦除,即泛型信息只存在於代碼編譯階段,在進入 JVM 以前,與泛型相關的信息會被擦除掉。對於下面的代碼,在運行階段建立 Array 時,你必須明確指明其類型,可是此時泛型信息已經被擦除,致使出現找不到類型的異常。
object ScalaApp extends App { def makePair[T](first: T, second: T) = { // 建立以一個數組 並賦值 val r = new Array[T](2); r(0) = first; r(1) = second; r } }
Scala 針對這個問題,提供了 ClassTag 上下文界定,即把泛型的信息存儲在 ClassTag 中,這樣在運行階段須要時,只須要從 ClassTag 中進行獲取便可。其語法爲 T : ClassTag
,示例以下:
import scala.reflect._ object ScalaApp extends App { def makePair[T : ClassTag](first: T, second: T) = { val r = new Array[T](2); r(0) = first; r(1) = second; r } }
2.1 小節介紹了類型上界的限定,Scala 同時也支持下界的限定,語法爲:U >: T
,即 U 必須是類型 T 的超類或自己。
// 首席執行官 class CEO // 部門經理 class Manager extends CEO // 本公司普通員工 class Employee extends Manager // 其餘公司人員 class OtherCompany object ScalaApp extends App { // 限定:只有本公司部門經理以上人員才能獲取權限 def Check[T >: Manager](t: T): T = { println("得到審覈權限") t } // 錯誤寫法: 省略泛型參數後,如下全部人都能得到權限,顯然這是不正確的 Check(new CEO) Check(new Manager) Check(new Employee) Check(new OtherCompany) // 正確寫法,傳入泛型參數 Check[CEO](new CEO) Check[Manager](new Manager) /* * 如下兩條語句沒法經過編譯,異常信息爲: * do not conform to method Check's type parameter bounds(不符合方法 Check 的類型參數邊界) * 這種狀況就完成了下界限制,即只有本公司經理及以上的人員才能得到審覈權限 */ Check[Employee](new Employee) Check[OtherCompany](new OtherCompany) }
類型變量能夠同時有上界和下界。 寫法爲 :T > : Lower <: Upper
;
不能同時有多個上界或多個下界 。但能夠要求一個類型實現多個特質,寫法爲 :
T < : Comparable[T] with Serializable with Cloneable
;
你能夠有多個上下文界定,寫法爲 T : Ordering : ClassTag
。
上文中使用到 Ordering 和 Ordered 特質,它們最主要的區別在於分別繼承自不一樣的 Java 接口:Comparable 和 Comparator:
爲何 Java 中要同時給出這兩個比較接口,這是由於你要比較的對象不必定實現了 Comparable 接口,而你又想對其進行比較,這時候固然你能夠修改代碼實現 Comparable,可是若是這個類你沒法修改 (如源碼中的類),這時候就可使用外置的比較器。一樣的問題在 Scala 中固然也會出現,因此 Scala 分別使用了 Ordering 和 Ordered 來繼承它們。
下面分別給出 Java 中 Comparable 和 Comparator 接口的使用示例:
import java.util.Arrays; // 實現 Comparable 接口 public class Person implements Comparable<Person> { private String name; private int age; Person(String name,int age) {this.name=name;this.age=age;} @Override public String toString() { return name+":"+age; } // 核心的方法是重寫比較規則,按照年齡進行排序 @Override public int compareTo(Person person) { return this.age - person.age; } public static void main(String[] args) { Person[] peoples= {new Person("hei", 66), new Person("bai", 55), new Person("ying", 77)}; Arrays.sort(peoples); Arrays.stream(peoples).forEach(System.out::println); } } 輸出: bai:55 hei:66 ying:77
import java.util.Arrays; import java.util.Comparator; public class Person { private String name; private int age; Person(String name,int age) {this.name=name;this.age=age;} @Override public String toString() { return name+":"+age; } public static void main(String[] args) { Person[] peoples= {new Person("hei", 66), new Person("bai", 55), new Person("ying", 77)}; // 這裏爲了直觀直接使用匿名內部類,實現 Comparator 接口 //若是是 Java8 你也能夠寫成 Arrays.sort(peoples, Comparator.comparingInt(o -> o.age)); Arrays.sort(peoples, new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { return o1.age-o2.age; } }); Arrays.stream(peoples).forEach(System.out::println); } }
使用外置比較器還有一個好處,就是你能夠隨時定義其排序規則:
// 按照年齡大小排序 Arrays.sort(peoples, Comparator.comparingInt(o -> o.age)); Arrays.stream(peoples).forEach(System.out::println); // 按照名字長度倒序排列 Arrays.sort(peoples, Comparator.comparingInt(o -> -o.name.length())); Arrays.stream(peoples).forEach(System.out::println);
這裏再次給出上下文界定中的示例代碼做爲回顧:
// 1.定義一我的員類 class Person(val name: String, val age: Int) { override def toString: String = name + ":" + age } // 2.繼承 Ordering[T],實現自定義比較器,這個比較器就是一個外置比較器 class PersonOrdering extends Ordering[Person] { override def compare(x: Person, y: Person): Int = if (x.age > y.age) 1 else -1 } class Pair[T](val first: T, val second: T) { def smaller(implicit ord: Ordering[T]): T = if (ord.compare(first, second) < 0) first else second } object ScalaApp extends App { val pair = new Pair(new Person("hei", 88), new Person("bai", 66)) // 3.在當前上下文定義隱式默認值,這就至關於傳入了外置比較器 implicit val ImpPersonOrdering = new PersonOrdering println(pair.smaller) //輸出: bai:66 }
使用上下文界定和 Ordering 帶來的好處是:傳入 Pair
中的參數不必定須要可比較,只要在比較時傳入外置比較器便可。
須要注意的是因爲隱式默認值二義性的限制,你不能像上面 Java 代碼同樣,在同一個上下文做用域中傳入兩個外置比較器,即下面的代碼是沒法經過編譯的。可是你能夠在不一樣的上下文做用域中引入不一樣的隱式默認值,即便用不一樣的外置比較器。
implicit val ImpPersonOrdering = new PersonOrdering println(pair.smaller) implicit val ImpPersonOrdering2 = new PersonOrdering println(pair.smaller)
在實際編碼中,一般須要把泛型限定在某個範圍內,好比限定爲某個類及其子類。所以 Scala 和 Java 同樣引入了通配符這個概念,用於限定泛型的範圍。不一樣的是 Java 使用 ?
表示通配符,Scala 使用 _
表示通配符。
class Ceo(val name: String) { override def toString: String = name } class Manager(name: String) extends Ceo(name) class Employee(name: String) extends Manager(name) class Pair[T](val first: T, val second: T) { override def toString: String = "first:" + first + ", second: " + second } object ScalaApp extends App { // 限定部門經理及如下的人才能夠組隊 def makePair(p: Pair[_ <: Manager]): Unit = {println(p)} makePair(new Pair(new Employee("heibai"), new Manager("ying"))) }
目前 Scala 中的通配符在某些複雜狀況下還不完善,以下面的語句在 Scala 2.12 中並不能經過編譯:
def min[T <: Comparable[_ >: T]](p: Pair[T]) ={}
可使用如下語法代替:
type SuperComparable[T] = Comparable[_ >: T] def min[T <: SuperComparable[T]](p: Pair[T]) = {}
更多大數據系列文章能夠參見 GitHub 開源項目: 大數據入門指南