Kotlin修煉指南(三)——奇技淫巧
Kotlin作為Android開發(fā)的首選語(yǔ)言,為開發(fā)者提供了大量的語(yǔ)法糖和技巧,讓開發(fā)者可以專注于需求開發(fā),而將語(yǔ)言所帶來的影響減少到最少。Java和Kotlin最大的區(qū)別,實(shí)際上在于Kotlin的函數(shù)式編程思想以及語(yǔ)法,特別是lambda表達(dá)式,這是Kotlin效率高于Java開發(fā)的核心武器,在之前的文章中,已經(jīng)有比較詳細(xì)的講解了。
下面我將從幾個(gè)方面分別來給大家演示下Kotlin究竟是如何提高開發(fā)效率的。
語(yǔ)法糖
所謂語(yǔ)法糖,實(shí)際上就是對(duì)Java原生寫法進(jìn)行的封裝,雖然不用也能寫,但是用了絕對(duì)回不去。
字符串模版
"${xxxBean.type}"
字符串模版保證了String的完整性,這也是大部分現(xiàn)代語(yǔ)言都會(huì)有的功能,有了字符串模板,就可以不再使用+進(jìn)行拼接,不但更方便,也讓字符串的語(yǔ)義更加明確。
Raw string
val?str1?=?"abc"
val?str2?=?"""value1\n
????value2
????value3
????"value4"
????""".trimMargin()
三引號(hào)代表Raw string,即三引號(hào)內(nèi)所有內(nèi)容均為string,即使有需要轉(zhuǎn)義的字符,也不用特殊處理。
使用trimMargin來去除每行開頭的空格
強(qiáng)化Switch
Kotlin中的when函數(shù),解除了Java中的switch的很多限制,并且拓展了很多方便的功能。
fun?getValue(a:?Int)?=?when?{
????a?>?0?->?"A"
????a?0?->?"N"
????a.hashCode()?==?0x108?->?"XYS"
????else?->?"ZJ"
}
在使用上更加靈活,同時(shí)讓選擇分支語(yǔ)句更加容易理解。
語(yǔ)句 Vs 表達(dá)式
kotlin中大部分關(guān)鍵字都是表達(dá)式。
表達(dá)式有值,并且能作為另一個(gè)表達(dá)式的一部分使用,這是函數(shù)式編程的基礎(chǔ) 語(yǔ)句總是包圍著它的代碼塊中的代碼元素,并且沒有自己的值,例如Java中的if\else\Switch等。
在Kotlin中,一個(gè)if語(yǔ)句是可以直接給一個(gè)變量賦值的,這就是表達(dá)式,它有返回值。
val?status?=?when?{}
????xxx?->?{}
????xxx?->?{}
????else?->?{}
}
這種方式比Java節(jié)省了太多的代碼,所以Kotlin中不再需要三目表達(dá)式了,直接通過if/else即可。
fun?max(a:?Int,?b:?Int)?=?if?(a?>?b)?a?else?b
延遲初始化
在Kotlin中,成員變量的值被嚴(yán)格區(qū)分可空和非可空,其中非可空的變量值,要么在聲明的時(shí)候進(jìn)行初始化,要么通過延遲加載的方式進(jìn)行初始化,一般來說,有兩種方式來進(jìn)行延遲加載。
lazy
通過lazy函數(shù),可以實(shí)現(xiàn)在首次使用到的時(shí)候才去實(shí)例化。
private?val?xxxxFragment?by?lazy?{
????XXXXFragment().apply?{
????????arguments?=?Bundle().apply?{
????????}
????}
}
lateinit
通過lateinit,自己控制變量的初始化。
private?lateinit?var?iv:?ImageView
這兩種方式各有各的使用場(chǎng)景:
by lazy 修飾val的變量 lateinit 修飾var的變量,且變量是非空的類型
data class
data class是Kotlin中一個(gè)用來生成模板代碼的語(yǔ)法糖,在Java中,定義的實(shí)體類,通常會(huì)有很多的模板代碼,大部分情況下,我們都是通過一個(gè)工具插件來生成,而在Kotlin中,則更加簡(jiǎn)單。
第一種方式實(shí)際上是Kotlin對(duì)構(gòu)造函數(shù)的優(yōu)化,省略了構(gòu)造函數(shù)的實(shí)體,直接通過參數(shù)聲明的方式進(jìn)行了創(chuàng)建。
//?Kotlin會(huì)為類的參數(shù)自動(dòng)實(shí)現(xiàn)get?set方法
class?User(val?name:?String,?val?age:?Int,?val?gender:?Int,?var?address:?String)
第二種方式則是借助data關(guān)鍵字,生成Kotlin中定義好的實(shí)體類。
//?用data關(guān)鍵詞來聲明一個(gè)數(shù)據(jù)類,除了會(huì)自動(dòng)實(shí)現(xiàn)get?set,同時(shí)還會(huì)自動(dòng)生成equals?hashcode?toString
data?class?User(val?name:?String,?val?age:?Int,?val?gender:?Int,?var?address:?String)
object
object在Kotlin中是一個(gè)比較難理解的概念,和Java中的Object完全不同,后面會(huì)有單獨(dú)的文章來介紹object,這里先簡(jiǎn)單的看下Kotlin通過object提供的語(yǔ)法糖。
object,其實(shí)可以把它理解成:定義一個(gè)類并創(chuàng)建該類的一個(gè)實(shí)例。
所以object的一個(gè)功能,就是快速創(chuàng)建一個(gè)單例模式。
例如在代碼中經(jīng)常寫的:
object?ThreadUtil?{
????fun?onMainThread(runnable:?Runnable)?{
????????val?mainHandler?=?Handler(Looper.getMainLooper())
????????mainHandler.post(runnable)
????}
}
簡(jiǎn)化下實(shí)際上就是下面的代碼。
object?Singleton?{
????fun?xxx()?{
????}
}
反編譯后看生成代碼,這就是一個(gè)典型的餓漢式單例,借助靜態(tài)代碼塊初始化的鎖,初始化單例實(shí)例,從而實(shí)現(xiàn)單例效果。
public?final?class?Singleton?{
???public?static?final?Singleton?INSTANCE;
???public?final?void?xxx()?{
???}
???private?Singleton()?{
???}
???static?{
??????Singleton?var0?=?new?Singleton();
??????INSTANCE?=?var0;
???}
}
通過object代替匿名內(nèi)部類
這是object的另一個(gè)比較常用的地方,也符合了object的語(yǔ)義,定義一個(gè)類,并生成該類的實(shí)例,也就是需要?jiǎng)?chuàng)建的匿名內(nèi)部類。
viewPager.addOnPageChangeListener(object?:?ViewPager.OnPageChangeListener?{
????override?fun?onPageScrollStateChanged(state:?Int)?{}
????override?fun?onPageScrolled(position:?Int,?positionOffset:?Float,?positionOffsetPixels:?Int)?{}
????override?fun?onPageSelected(position:?Int)?{}
});
companion object
由于Kotlin中沒有靜態(tài)函數(shù),所以在Kotlin中,可以使用companion object替代Java中的static修飾。
編譯器會(huì)自動(dòng)生成了一個(gè)叫做Companion的靜態(tài)內(nèi)部類。
在Java中調(diào)用伴生對(duì)象,可以使用User.Companion.isMale(1)
class?User?{
????companion?object?{
????????const?val?DEFAULT_USER_AGE?=?30
????}
????
????fun?test(){}
}
//?later,?accessed?like?you?would?a?static?variable:
user.age?=?User.DEFAULT_USER_AGE
Kotlin函數(shù)
在Kotlin的基礎(chǔ)庫(kù)中,系統(tǒng)提供了大量針對(duì)函數(shù)的優(yōu)化,解決了很多在Java代碼中寫起來不太爽的地方。
顯式參數(shù)
在Java中,當(dāng)一個(gè)函數(shù)的參數(shù)值太多時(shí),需要一個(gè)個(gè)對(duì)齊參數(shù),雖然可以通過IDE的快捷提示等功能來展示,但始終用起來不太方便,而在Kotlin中,除了像Java中那樣按順序的傳遞參數(shù)外,還可以通過指定參數(shù)名的方式進(jìn)行參數(shù)傳遞。
fun?test(name:?String,?age:?Int)?{
}
test(name?=?"xys",?age?=?18)
參數(shù)含義一目了然,提高了代碼的可讀性。
參數(shù)默認(rèn)值
fun?test(name:?String?=?"xys",?age:?Int)?{
}
fun?a()?{
????test(age?=?18)
}
通過參數(shù)默認(rèn)值,可以避免Java下大量參數(shù)下的重載函數(shù),當(dāng)某個(gè)參數(shù)可以使用默認(rèn)值時(shí),就不用顯示的聲明了,類似Java中的不同參數(shù)的重載函數(shù)。
在Java、Kotlin混編的時(shí)候,無(wú)法避免的會(huì)混合調(diào)用,可以通過@JvmOverloads注解,給Java代碼生成重載的函數(shù)。
拓展函數(shù)
拓展函數(shù)可以說是Kotlin最為重要的黑魔法之一了,通過拓展函數(shù),可以給一些系統(tǒng)類添加原本沒有的函數(shù),極大的提高了函數(shù)的可拓展性。
fun?Activity.toast(msg:?String)?{
????Toast.makeText(this,?msg,?Toast.LENGTH_SHORT).show()
}
拓展屬性
與拓展函數(shù)類似,拓展屬性可以給現(xiàn)有屬性拓展自定義的實(shí)現(xiàn)。
val?String.lastChar:?Char
????get()?=?get(length?-?1)
拓展功能看上去比較神奇,但大家可以通過查看Kotlin生成的class代碼,反編譯成的Java代碼來看它具體的實(shí)現(xiàn)方法。
對(duì)于擴(kuò)展函數(shù)來說,轉(zhuǎn)化為Java代碼的時(shí)候,其實(shí)就是生成一個(gè)靜態(tài)的函數(shù),這個(gè)靜態(tài)函數(shù)的第一個(gè)參數(shù)就是該類的實(shí)例對(duì)象,所以這樣把類的實(shí)例傳入函數(shù)以后,函數(shù)內(nèi)部就可以訪問到類的公有方法。
擴(kuò)展屬性也是類似,獲取的擴(kuò)展屬性會(huì)生成為一個(gè)靜態(tài)的get函數(shù),同時(shí)這個(gè)靜態(tài)函數(shù)的第一個(gè)參數(shù)就是該類的實(shí)例對(duì)象,設(shè)置的擴(kuò)展屬性會(huì)轉(zhuǎn)化為一個(gè)靜態(tài)的set函數(shù),同時(shí)這個(gè)靜態(tài)函數(shù)的第一個(gè)參數(shù)就是該類的實(shí)例對(duì)象。函數(shù)內(nèi)部可以訪問公有的方法和屬性。
在了解了其實(shí)現(xiàn)原理后,可以發(fā)現(xiàn),拓展函數(shù)一定是static的,且不能被override,也不存在運(yùn)行時(shí)類型,其類型在編譯時(shí)就已經(jīng)確定,同時(shí)擴(kuò)展函數(shù)和擴(kuò)展屬性內(nèi)只能訪問到類的公有方法和屬性,私有的和protected同樣是不能訪問的。
拓展函數(shù)和拓展屬性只是Kotlin語(yǔ)法的障眼法,并沒有實(shí)際的去修改一個(gè)類
嵌套函數(shù)
函數(shù)是Kotlin中的第一公民,所以函數(shù)可以出現(xiàn)在Kotlin中的任何一個(gè)地方,包括在一個(gè)函數(shù)中。
在一個(gè)函數(shù)中定義另一個(gè)函數(shù),可以很好的將這個(gè)函數(shù)的使用限制在當(dāng)前的外層函數(shù)中,避免對(duì)外暴露不必要的接口,同時(shí)還能避免重復(fù)的模板代碼,例如下面這個(gè)例子。
class?User(val?id:?Int,?val?name:?String,?val?address:?String,?val?email:?String)
fun?check(user:?User)?{
????if?(user.name.isEmpty())?{
????????throw?IllegalArgumentException("Can't?save?user?${user.id}:?empty?Name")
????}
????if?(user.address.isEmpty())?{
????????throw?IllegalArgumentException("Can't?save?user?${user.id}:?empty?Address")
????}
????if?(user.email.isEmpty())?{
????????throw?IllegalArgumentException("Can't?save?user?${user.id}:?empty?Email")
????}
????//?...
}
通過嵌套函數(shù)實(shí)現(xiàn)。
fun?saveUser2(user:?User)?{
????fun?validate(value:?String,?fildName:?String)?{
????????if?(value.isEmpty())?{
????????????throw?IllegalArgumentException("Can't?save?user?${user.id}:?empty?$fildName")
????????}
????}
????validate(user.name,?"Name")
????validate(user.address,?"Address")
????validate(user.email,?"Email")
????//?...
}
工具類函數(shù)
由于在Kotlin中,函數(shù)可以脫離類而獨(dú)立存在,所以這對(duì)于工具類函數(shù)來說,就非常方便了,不用再定義一個(gè)ToolUtil類,而可以直接寫在文件中。
作用域函數(shù)
作用域函數(shù)在Kotlin修煉指南(一)中已經(jīng)有詳細(xì)介紹了。
設(shè)計(jì)模式
設(shè)計(jì)模式最早是在面向?qū)ο缶幊痰幕A(chǔ)上提出來的編程范式,但是對(duì)于函數(shù)式編程來說,有很多定義都過于教條了,所以,現(xiàn)代式的編程語(yǔ)言,通過很多語(yǔ)法上的定義,就已經(jīng)實(shí)現(xiàn)了很多種設(shè)計(jì)模式。
單例模式
前面已經(jīng)提到了,通過object class,就可以很輕松的實(shí)現(xiàn)一個(gè)線程安全的單例類。
靜態(tài)工廠模式
借助運(yùn)算符重載,可以很方便的實(shí)現(xiàn)靜態(tài)工廠模式。
interface?Car?{
????val?brand:?String
????companion?object?{
????????operator?fun?invoke(type:?CarType):?Car?{
????????????return?when?(type)?{
????????????????CarType.AUDI?->?Audi()????
????????????????CarType.BMW?->?BMW()
????????????}
????????}
????}
}
通過重載了invoke()函數(shù),在調(diào)用Car(CarType.BMW)的時(shí)候,就創(chuàng)建好了對(duì)應(yīng)的工廠實(shí)例。
代理模式 策略模式
代理模式,或者說策略模式,都可以通過Kotlin中的類委托來實(shí)現(xiàn)。
interface?BaseTTS?{
????fun?doTTS()
}
class?BaiDuTTS?:?BaseTTS?{
????override?fun?doTTS()?{
????????print("BaiDu")
????}
}
class?TencentTTS?:?BaseTTS?{
????override?fun?doTTS()?{
????????print("Tencent")
????}
}
class?TTSCategory(tts:?BaseTTS)?:?BaseTTS?by?tts
fun?doTest()?{
????TTSCategory(BaiDuTTS()).doTTS()
}
通過類委托,將tts的實(shí)現(xiàn)代理出來。
更進(jìn)一步,可以通過匿名類的方式,直接創(chuàng)建代理類的實(shí)現(xiàn)。
interface?BaseTTS?{
????fun?doTTS()
}
class?TTSCategory(tts:?BaseTTS)?:?BaseTTS?by?tts?{
????override?fun?doTTS()?{
????????print("Do?tts")
????}
}
而當(dāng)策略中只有一個(gè)函數(shù)的時(shí)候,還可以進(jìn)一步簡(jiǎn)化,把策略直接封裝成Lambda表達(dá)式。
class?TTSCategory(val?strategy:?()?->?Unit)?{
????fun?doTTS()?{
????????strategy.invoke()
????}
}
fun?test()?{
????TTSCategory?{?print("Do?tts")?}.doTTS()
}
裝飾器模式
同樣是通過類委托功能,還可以實(shí)現(xiàn)裝飾器模式。
裝飾器模式是為了解決繼承導(dǎo)致類行為變更的問題產(chǎn)生的。如果需要在使用一個(gè)類的同時(shí),又要修改該類的一些函數(shù)的實(shí)現(xiàn),這時(shí)候就可以使用裝飾器模式,創(chuàng)建一個(gè)裝飾器類,實(shí)現(xiàn)與原始類一樣的接口并將原來的類的實(shí)例作為一個(gè)成員變量。裝飾器類與原始類擁有相同行為的方法不用修改,只需要直接轉(zhuǎn)發(fā)給原始類的實(shí)例,需要修改的函數(shù),實(shí)現(xiàn)新的功能即可。
但這里的問題是,當(dāng)一個(gè)原始類需要實(shí)現(xiàn)的函數(shù)很多時(shí),而裝飾器類又只需要修改很少的函數(shù)時(shí),就會(huì)產(chǎn)生大量的模板代碼,所以這個(gè)時(shí)候,借助類委托,就可以極大的減少這種模板代碼的產(chǎn)生。
class?ListDecorator(val?innerSet:?List?=?listOf())?:?List?by?innerSet?{
????override?fun?contains(element:?T):?Boolean?{
????????print("Do?other?thing")
????????return?innerSet.contains(element)
????}
}
fun?test()?{
????val?contains?=?ListDecorator(listOf("ss")).contains("s")
}
通過反編譯代碼可以發(fā)現(xiàn),實(shí)際上編譯器幫助我們重寫了所有的未修改函數(shù)。

后續(xù)計(jì)劃
Kotlin有趣的地方還有很多,一篇文章很難全部寫完,所以后面的計(jì)劃如下。
集合與惰性序列 Kotlin DSL 操作符重載 sealed class KTX
修仙
Flutter Dojo開源至今,受到了很多Flutter學(xué)習(xí)者和愛好者的喜愛,也有越來越多的人加入到Flutter的學(xué)習(xí)中來,所以我建了個(gè)Flutter修仙群,但是人數(shù)太多,所以分成了【Flutter修仙指南】【Flutter修仙指北】【Flutter修仙指東】三個(gè)群,對(duì)Flutter感興趣的朋友,可以添加我的微信,注明加入Flutter修仙群,或者直接關(guān)注我的微信公眾號(hào)【Android群英傳】。

感興趣的朋友可以加我微信【Tomcat_xu】,我拉你入群。
項(xiàng)目地址:
https://github.com/xuyisheng/flutter_dojo
