2018年第47周-scala入門-類型參數

類型參數相似於Java中的泛型。注意了,是類型參數,不是參數類型, 顧名思義,就是把類型做爲參數。跟Java泛型同樣,在集合,類,函數中定義參數類型,而後保證 使用到該類型參數的地方,只能用這種類型,不然編譯器報錯。在編譯期就能發現錯誤能夠大大下降開發成本。java

這裏說的成本我是很想討論下,開發成本,不只僅指的是錢。如你在編寫代碼時,IDE就提示你錯誤了。這時候你就不用浪費時間去點擊運行才發現錯誤,從而節省了你的時間,你可能會說時間就是錢啊,實際不那麼是,由於得看角度,你的時間或許是你的金錢,但對於老闆來講不是,由於你一天不管幹什麼,發呆、百度資料或作了個功能,老闆都是給同樣的錢。但開發成本,我以爲可以全面客觀的描述這個項目花的勞動時間及資源。

泛型類

泛型類,顧名思義,其實就是在類的聲明中,定義一些泛型類型,而後在類的內部,好比field或者method中使用。
使用泛型類,同城須要對類中的某些成員, 好比某些field或method中的參數或變量,進行統一的類型限制,這樣能夠保證程序更好的健壯性和穩定性。在編譯期就能發現錯誤。
若是不是使用泛型進行統一的類型限制,那麼在運行時發現錯誤,則處理錯誤的成本就要高不少了。
在使用類的時候,好比建立對象,將類型參數替換爲實際的類型便可。或者直接給使用了泛型類的field賦值是,scala會自動進行類型推斷。
例子:新生報到,每一個學生來自不一樣的地方,id多是Int,也多是Stringes6

clas Student[T](val localId:T){
  def getSchoolId(id:T) = "S-"+id+"-"+localId
}
val jc = new Student[Int](700)
jc.getSchoolId("jc");
jc.getSchoolId(10);

泛型函數

泛型函數,與泛型類相似,能夠給某個函數在聲明時指定泛型類型,而後在函數體內,多個變量或返回值之間,就可使用泛型類型進行聲明,從而對某個特殊的變量,或者多個變量,進行強制性的類型限制。
與泛型類同樣,你能夠經過使用了泛型類型的變量傳遞值來讓scala自動推斷泛型的是積累下,也能夠在調用函數時,手動指定函數類型。
例子:卡片售賣機,能夠指定卡片的內容,內容能夠是String類型或者Int類型數組

def getCard[T](content:T)={
  if(content.isInstanceOf[Int]) "card:001,"+content
   else if(content.isInstanceOf[String]) "card: this your card, "+content
   else "card: "+content
}

上邊界Bounds

在指定泛型類型的是偶,有時咱們須要對泛型類型的範圍進行界定,而不是能夠是任意的類型。好比,咱們可能要求某個泛型類型,它就必須是某個類的子類,這樣在程序中就能夠放心地調用泛型類型繼承弗雷的方法,程序才能正常的使用和運行。此時就可使用上下邊界Bounds的特性。
scala的上下邊界特性容許泛型類型必須是某個類的子類,或者必須是某個類的弗雷。
上邊界Bounds語法: T<:Person
例子:在派對上交朋友函數

class Person(val name:String){
   def sayHello = println("Hello, I'm "+ name)
   def makeFriends(p:Person){
      sayHello
      p.sayHello
   }
}

class Student(name:String) extends Person(name)
class Other(name:String)
class Party[T<:Person](p1:T,p2:T){
  def play = p1.makeFriends(p2);
}
scala> val s = new Student("jc")
s: Student = Student@718632e9

scala> val s2 = new Student("jc2")
s2: Student = Student@3540ee91

scala> val p = new Party(s,s2)
p: Party[Student] = Party@77240ed1

scala> p.play
Hello, I'm jc
Hello, I'm jc2


scala> val s3 = new Person("jc2")
s3: Person = Person@38c9c7a0

scala> val p = new Party(s,s3)
p: Party[Person] = Party@fa7e87b

scala> val o = new Other("o")
o: Other = Other@40cecfc0

scala> val p = new Party(s,o)
<console>:14: error: inferred type arguments [Object] do not conform to class Party's type parameter bounds [T <: Person]
       val p = new Party(s,o)
               ^
<console>:14: error: type mismatch;
 found   : Student
 required: T
       val p = new Party(s,o)
                         ^
<console>:14: error: type mismatch;
 found   : Other
 required: T
       val p = new Party(s,o)
                           ^

語法[T<:Person],只要是Person類或及其子類便可。ui

下邊界Bounds

下邊界,指定類型必須是某個類的父類
下邊界Bounds語法: T>:Child
例子:領身份證this

class Father(val name:String)
class Child(name:String) extends Father(name)
class GrandChild(name:String) extends Child(name)

def getIdCard[R>:Child](p:R){
  if(p.getClass == classOf[Child]) println("please tell us your parents' names.")  
   else if(p.getClass == classOf[Father]) println("sign your name to get your child's id card.")  
  else println("sorry, you are not allowed to get id card.")
}
val c = new Child("jc child")
val p = new Father("jc father")
getIdCard(c)
getIdCard(p)
val g = new GrandChild("jc grandechild")
getIdCard(g)  //可執行成功

// 想要編譯器能發現錯誤,就只能在調用函數式,指定類型參數
scala> getIdCard[GrandChild](g)
<console>:16: error: type arguments [GrandChild] do not conform to method getIdCard's type parameter bounds [R >: Child]
       getIdCard[GrandChild](g)

View Bounds

上下邊界Bounds,雖然可讓一種泛型類型,支持父子關係的多種類型。可是,在某個類與上下邊界Bounds指定的父子類型範圍內的類都沒有任何關係,則默認是確定不能接受的。
然而,View Bounds做爲一種上下邊界Bounds的增強版,支持對類型進行隱時轉換,將指定的類型進行隱式轉換後,再判斷是否在邊界指定得了類型範圍內。
上邊界Bounds語法: T>:Person
例子:跟小狗交朋友scala

class Person(val name:String){
  def sayHello = println("Hello, I'm " + name)
  def makeFriends(p:Person){
    sayHello
    p.sayHello
   }
}

class Student(name:String) extends Person(name)


//特定於法,<% 表明Person及Person如下的類,能夠包括隱式轉換的
class Party[T<%Person](p1:T,p2:T){
  def play = p1.makeFriends(p2);
}



scala> val s = new Student("jc")
s: Student = Student@3e1e52b8

scala>  val p = new Person("jcp")
p: Person = Person@44f8d156

scala> val party = new Party(s,p)
party: Party[Person] = Party@587f3671

scala> party.play
Hello, I'm jc
Hello, I'm jcp


class Dog(val name:String){def sayHello = println("Wang, Wang, I'm "+ name)}


scala> val d = new Dog("jdog")
d: Dog = Dog@729dc481

//報錯,由於沒有指定隱式轉換
scala> val party = new Party(s,d)
<console>:14: error: No implicit view available from Object => Person.
       val party = new Party(s,d)
                   ^


implicit def dog2person(dog: Object): Person= if(dog.isInstanceOf[Dog]){val _dog = dog.asInstanceOf[Dog]; new Person(_dog.name)} else Nil
 
//需指定泛型類型,否則party.play就一直卡住
scala> val party = new Party[Person](s,d)
party: Party[Person] = Party@702a694f

scala> party.play
Hello, I'm jc
Hello, I'm jdog

Context Bounds

Context Bounds是一種特殊的Bounds,它會根據泛型類型的聲明,好比「T:類型」要求必須存在一個類型爲「類型[T]」的隱式值。
就是可使用scala的隱式類,而且能夠控制參數類型的一致性
例子: 使用scala內置的比較器比較大小code

class Calculator[T: Ordering](val number1:T, val number2:T){
  def max(implicit order: Ordering[T]) = if(order.compare(number1,number2)>0) number1 else number2
}
scala> val c = new Calculator(1,2)
c: Calculator[Int] = Calculator@c62b064
 
scala> c.max
res6: Int = 2

scala> val c = new Calculator(1,"2")
<console>:13: error: No implicit Ordering defined for Any.
       val c = new Calculator(1,"2")
               ^

scala> val c = new Calculator("3","2")
c: Calculator[String] = Calculator@409c4578

scala> c.max
res7: String = 3

泛型數組

若是想實例化一個泛型數組,需運用到Manifest Context Bounds。也就是說,若是數組元素類型爲T的話,須要爲類或函數定義[T:Manifest]類型參數。
簡單直白地說就是 固定語法,類和函數必須定義[T:Manifest],其裏面才能使用泛型數組。orm

例子: 打包飯菜(不一樣食物類型分開打包)對象

class Meat(val name:String)
class Vegetable(val name:String)

def packageFood[T:Manifest](food: T*) = {
  val foodPackage = new Array[T](food.length)
  for(i <-0 until food.length) foodPackage(i) = food(i)
  foodPackage
}

scala> packageFood(new Meat("m1"),new Meat("m2"))
res8: Array[Meat] = Array(Meat@18f4a2ae, Meat@7e319726)

//因爲scala有自動推斷功能,因此想要編譯期發現錯誤,需指定類型參數
scala> packageFood(new Meat("m1"),new Vegetable("v2"))
res9: Array[Object] = Array(Meat@4c1bd6ce, Vegetable@16a7770b)

//因爲scala有自動推斷功能,因此想要編譯期發現錯誤,需指定類型參數
scala> packageFood[Meat](new Meat("m1"),new Vegetable("v2"))
<console>:16: error: type mismatch;
 found   : Vegetable
 required: Meat
       packageFood[Meat](new Meat("m1"),new Vegetable("v2"))

協變和逆變

scala的協變和逆變是很是有特點的,徹底解決了java中的泛型的一大遺憾。
舉例子說,java中,若是有Professional是Master的子類,那麼Card[Professional]是否是Card[Master]的子類?答案是:不是的。
例子:進入會場

class Master
class Professional extends Master

//大師以及大師級別如下的名片均可以進入會場
//
class Card[+T] (val name:String)

def enterMeet(card:Card[Master]){
  println("welcome to have this meeting")
}
scala> val p = new Card[Professional]("p")
p: Card[Professional] = Card@49f8e77c

scala> enterMeet(p)
welcome to have this meeting


scala> val m = new Card[Master]("m")
m: Card[Master] = Card@125d05e2

scala> enterMeet(m)
welcome to have this meeting

協變,在上述例子也就是Card[Profession]是Card[Master]的子類,協變的語法是[+T],對比[T]:

scala> class Card[T] (val name:String)
defined class Card

scala> val p = new Card[Professional]("p")
p: Card[Professional] = Card@6732963b

def enterMeet(card:Card[Master]){
  println("welcome to have this meeting")
}

scala> enterMeet(p)
<console>:15: error: type mismatch;
 found   : Card[Professional]
 required: Card[Master]
       enterMeet(p)

逆變,也就是上述例子Card[Master]是Card[Profession]的父類,逆變的語法是[-T],對比[+T]:

scala> class Card[-T](val name:String)
defined class Card

def enterMeet(card:Card[Master]){
  println("welcome to have this meeting")
}

scala> val p = new Card[Professional]("p")
p: Card[Professional] = Card@584ff37c

scala> enterMeet(p)
<console>:15: error: type mismatch;
 found   : Card[Professional]
 required: Card[Master]
       enterMeet(p)
                 ^

//只要專家級別的名片就能夠進入會場,若是大師級別的過來了,固然能夠了!
def enterMeet(card:Card[Professional]){
  println("welcome to have this meeting")
}

scala> enterMeet(p)
welcome to have this meeting

scala> val m = new Card[Master]("m")
m: Card[Master] = Card@7ef67f5d

scala> enterMeet(p)
welcome to have this meeting

要點,協變和逆變的語法,可讓本來控制泛型很死的類或函數,擴展到其泛型的父類或子類:
協變,可讓函數enterMeet(card:Card[Master]),本來是隻能Master的Card參數,如今只須要Card[+T],便可傳入Card[Master]和Card[Professional]參數。也就是能夠傳入某類及其子類的參數。

協變跟上邊界[T<:]不同,協變討論的是Card[Professional]是否是Card[Master]的子類。而上邊界[T<:]討論的是Professional是否是Master的子類。從數學上來比喻它們不同就是,定義域不同,因此協變跟上邊界[T<:Card]不是同個東西,更加沒有好對比。

逆變,理解了協變,那逆變就更好理解了,可讓函數enterMeet(card:Card[Professional]),本來是隻能Professional的Card參數,如今只須要Card[-T],便可傳入Card[Master]和Card[Professional]參數。也就是能夠傳入某類及其父類的參數。

相關文章
相關標籤/搜索