什么時(shí)候需要使用泛型
在講到泛型之前,先寫(xiě)一段代碼(文中的代碼都是Swift書(shū)寫(xiě))。
func addTwoInt(_ a:Int,_ b:Int)->Int{
return a+b
}
這是一個(gè)很常見(jiàn)的也很簡(jiǎn)單的Int類型的加法函數(shù)。函數(shù) addTwoInt 將傳入的兩個(gè)參數(shù) a和b的和返回。
再看一個(gè)函數(shù):
func addTwoFloat(_ a:CGFloat,_ b:CGFloat)->CGFloat{
return a+b
}
這個(gè)函數(shù)是將兩個(gè) CGFloat類型的和輸出。
如果還需要寫(xiě)Double類型的加法,按照上面的方式再寫(xiě)一個(gè)函數(shù)就可以了。面對(duì)越來(lái)越多的類型,需要寫(xiě)的函數(shù)也越來(lái)越多,幸好,我們能夠使用+操作符的類型并不多。盡管如此,這樣的方式寫(xiě)代碼我們依舊不能忍受。有什么辦法讓他們統(tǒng)一一下呢?
畢竟,這樣一大堆類似的函數(shù)除了類型不一樣,其余的操作都是完全同質(zhì)的。
有兩種方式可以幫助我們使用一個(gè)函數(shù)搞定上面說(shuō)的所有的類型的加法。
-
使用Swift中的
Any類型替代
我們可以定義以下函數(shù)來(lái)代替上面的幾個(gè)類型的加法函數(shù)
func addTwoNum(_ a:Any,_ b:Any)->Any{
if let intA = a as? Int,let intB = b as? Int
{
return intA + intB
}
if let floatA = a as? CGFloat,let floatB = b as? CGFloat
{
return floatA + floatB
}
fatalError()
}
-
使用泛型
下面的代碼使用泛型替代
func addTwo<T>(_ a:T,_ b:T)->T{
if let intA = a as? Int,let intB = b as? Int
{
return intA + intB as! T
}
if let floatA = a as? CGFloat,let floatB = b as? CGFloat
{
return floatA + floatB as! T
}
fatalError()
}
這兩種方式都可以解決上面說(shuō)的將多個(gè)除類型不同的同質(zhì)化函數(shù)轉(zhuǎn)化成一個(gè)。看起來(lái)似乎沒(méi)什么不一樣的地方。
其實(shí)不是的!看下面的解釋
在Swift中,
Any類型會(huì)避開(kāi)編譯器的類型檢測(cè),即使是我們輸入了非數(shù)字類型調(diào)用addTwoNum函數(shù),或者我們返回的不是一個(gè)與輸入?yún)?shù)同類型的返回值,編譯器在編譯的時(shí)候也不會(huì)報(bào)錯(cuò)。只有在運(yùn)行時(shí)發(fā)現(xiàn)類型不對(duì)導(dǎo)致Crash。
而泛型自帶了類型推斷,也即是在編譯過(guò)程中,會(huì)進(jìn)行類型推斷。
addTwo<T>可以理解成一個(gè)函數(shù)族,編譯器會(huì)識(shí)別其中的類型T,后續(xù)的參數(shù)和返回值必須也是類型T,編譯才能通過(guò)。這樣保證了函數(shù)使用的確定性。
這樣的情況,當(dāng)然是選擇使用泛型嘛!
如果泛型只有這些簡(jiǎn)單的用處,那確實(shí)不怎么地,因?yàn)槲覀円廊贿€時(shí)需要在addTwo<T>中判斷它的實(shí)際類型,不然我們并不能進(jìn)行相應(yīng)的操作,這里的操作是 +。如何進(jìn)行修改呢?
給泛型添加約束
在addTwo<T>中,泛型T目前來(lái)說(shuō)只是一個(gè)位置的類型。任意類型都可以被做為參數(shù)帶進(jìn)來(lái),我們?cè)诤瘮?shù)內(nèi)部?jī)H僅實(shí)現(xiàn)了Int和CGFloat的 +操作。如果換成其他的類型,比如 String,那將返回一個(gè)致命錯(cuò)誤:fatalError()。這種設(shè)計(jì)顯然不合理。使用約束可以規(guī)定用戶能夠使用的類型。
所謂約束,并不是真正的約束,而是對(duì)泛型的可選范圍進(jìn)行調(diào)整。通常情況下,我們會(huì)使用需要泛型遵循必要的協(xié)議的方式實(shí)現(xiàn)。
一個(gè)例子:
func comparTwo<T:Comparable>( _ a:T,_ b:T) -> Bool {
return a>b
}
這是一個(gè)比較函數(shù)。規(guī)定了T必須遵循Comparable,Comparable是Swift中自帶的協(xié)議之一。只要遵循這個(gè)協(xié)議的類別,都可以使用>操作符號(hào)進(jìn)行比較, 返回一個(gè)布爾值。Int,String等都遵循了這個(gè)協(xié)議。
調(diào)用:

如果傳入不遵循
Comparable協(xié)議的類型的時(shí)候,比如NSArray,在編譯的時(shí)候就會(huì)報(bào)錯(cuò)。上圖中。
除此之外,遵循了相應(yīng)的協(xié)議,在comparTwo<T:Comparable>的函數(shù)中,不必像addTwo<T>一樣需要進(jìn)行類型判斷,代碼更加簡(jiǎn)潔。
Swift中一共有55個(gè)協(xié)議,并將讓它們分成了三大類,有興趣的可以看看這篇關(guān)于關(guān)于Swift的55個(gè)協(xié)議簡(jiǎn)介的文章。Swift中鼓勵(lì)我們使用協(xié)議,所以很多人說(shuō)這是面向協(xié)議編程,這里不討論。
返回到addTwo<T>中,我們需要一個(gè)可以進(jìn)行加法的協(xié)議。目前我在Swift的協(xié)議中并沒(méi)有找到。倒是在Int和CGFloat中看到它們分別重載了 +,-等等各種操作符。所以,使用加法協(xié)議看來(lái)是沒(méi)有現(xiàn)成的了。但是我們可以自己寫(xiě)一套協(xié)議NewProtocol,這套協(xié)議將定義如何將兩個(gè)類型進(jìn)行+操作,然后在每個(gè)可能使用的類型中,重載相應(yīng)的操作符實(shí)現(xiàn)。之后再讓addTwo<T>中的T遵循這個(gè)協(xié)議。接下來(lái)就跟使用comparTwo<T:Comparable>一樣使用addTwo<T:NewProtocol>。這已經(jīng) 屬于如何使用協(xié)議的范疇了,在這里也不再仔細(xì)討論。
其實(shí)我們已經(jīng)看出來(lái)了,在有已知的可用的協(xié)議的情況下,我們可以很方便的使用泛型做到將多個(gè)不同但類型同質(zhì)化函數(shù)合并成一個(gè)函數(shù)??扇绻F(xiàn)存的55個(gè)協(xié)議中,并沒(méi)有我們想要的協(xié)議,那我們就需要自己定義協(xié)議,這跟寫(xiě)很多個(gè)函數(shù)一樣,需要寫(xiě)不少東西。有沒(méi)有什么辦法,可以不使用協(xié)議而達(dá)到同樣的目的呢,答案是有的!
泛型和高階函數(shù)結(jié)合
上面提到了通過(guò)協(xié)議可以對(duì)一部分指定的類實(shí)現(xiàn)泛型編程。但是面對(duì)沒(méi)有現(xiàn)成協(xié)議的時(shí)候,我們還可以配合高階函數(shù)的使用來(lái)解決。
下面我通過(guò)另一種方式來(lái)實(shí)現(xiàn):
func addTwo<T>( _ a:T,_ b:T,_ sideFun:(T,T)->(T)) -> T {
return sideFun(a,b)
}
調(diào)用:
let intA:Int = 5
let intB:Int = 15
let floatC:CGFloat = 0.6
let floatD:CGFloat = 1.62
print(self.addTwo(intA, intB) { (a, b) -> Int in
return a+b
}) // 20
print(self.addTwo(floatC, floatD) { (a, b) -> CGFloat in
return a+b
}) // 2.22
是不是簡(jiǎn)潔了很多倍? 并且這個(gè)函數(shù)不僅適用于Int和CGFloat,還是用于一個(gè)String,Double等各種類型。同時(shí),仔細(xì)的你肯定發(fā)現(xiàn)了,這個(gè)函數(shù)不僅可以做加法,還可以做減法和其他更多的操作。我們看實(shí)際的調(diào)用:
// 針對(duì)String類型
let newString = self.addTwo("第一段字符", "第二段字符") { (a, b) -> String in
return a+b
}
print(newString) // 第一段字符第二段字符
// 減法
print(self.addTwo(intA, intB) { (a, b) -> Int in
return a-b
}) // -10
print(self.addTwo(floatC, floatD) { (a, b) -> CGFloat in
return a-b
}) // -1.02
看起來(lái)比使用協(xié)議的功能還要強(qiáng)大!但是這有個(gè)弊端就是:sideFun需要使用者自己去實(shí)現(xiàn),相應(yīng)的多增加了使用者需要書(shū)寫(xiě)的代碼,但是相對(duì)于這種方式帶來(lái)的便利,這種弊端可以忽略不計(jì)吧。
到此,我們聯(lián)想到了Swift中數(shù)組的一個(gè)函數(shù):
public func sorted(by areInIncreasingOrder: (Element, Element) -> Bool) -> [Element]
這是數(shù)組的排序函數(shù)。跟addTwo類似。但是sorted函數(shù)將的實(shí)際參數(shù)是slef本身(這是數(shù)組的擴(kuò)展)。sorted是Swift的一種泛型類型,跟addTwo的T一樣。
總結(jié)一下,之前說(shuō)的泛型可以代替一系列操作類似的類型。但是如果泛型和高階函數(shù)一起使用,它則可以代替一系列類似的函數(shù)形式。
單純的泛型,可以替代多種類型,進(jìn)行同類操作。結(jié)合高階函數(shù),泛型可以將具有相同的函數(shù)形式多種操作合為為一,比如addTwo,就是一種使用兩個(gè)參數(shù),結(jié)合一個(gè)函數(shù)參數(shù),輸出不同結(jié)果的函數(shù)形式。