Kotlin 總結(jié)分享
如果用一句話總結(jié)kotlin,那么就是:更好的java
類型申明
String a = "I an java";
Val a :String = "I an Kotlin"
為什么采用這種風(fēng)格辙培? 代碼的可讀性更好
增強的類型推到
val String = "I an Kotlin"
val int = 12
val long = 12L
val float = 13.14f
val double = 12.12
val double = 1.0e2 // 100
類型推導(dǎo)在很大程度上提高了開發(fā)效率,當(dāng)我們使用kotlin的時候,不再需要寫大量的類型
聲明函數(shù) 返回值類型
fun sum(x:Int,y:Int):Int{
return x+y
}
fun sum(x:Int,y:Int) = x+y
val 和var的使用規(guī)則
var代表了 變量罗岖,val 具有java中的final 關(guān)鍵字的效果,引用不可變腹躁,但是其引用的值是可以更改的
val bool = Book() //用val聲明的book對象的引用不可變
book.name = "kt"
優(yōu)先使用val 聲明一個本身不可變的變量是一種防御性編碼思維桑包,更加安全可靠,不可變的變量意味著更加容易推理纺非,越是復(fù)雜的業(yè)務(wù)邏輯哑了,優(yōu)勢越大,但是如何保證引用對象的不可變烧颖?--- 參考集合
高階函數(shù)與lambda
函數(shù)試語言一個典型的特征就在于 函數(shù)是頭等公民 ---- 我們不僅可以像類一樣在頂層直接定義一個函數(shù)弱左,還可以在一個函數(shù)內(nèi)部定義一個局部函數(shù)
fun foo(x:Int){
fun double(y:Int) = y*2
print(double(x))
}
>>> foo(1)
2
此外,我們還可以直接將函數(shù)像普通變量一樣傳遞給另一個函數(shù)炕淮,或在其他函數(shù)內(nèi)被返回
實例:函數(shù)作為參數(shù)的需求
現(xiàn)有關(guān)于國家的數(shù)據(jù)集List<Country> 如何篩選出歐洲的國家拆火?
data class Country(
val name:String,
val continent:String,
val population:Int
)
class CountryApp{
fun filterCountries(countries:List<Country>):List<Country>{
val res = mutableListOf<Country>()
for (c in countries){
if(c.continent == "EU"){
res.add(c)
}
}
return res
}
}
需求變化一:不僅想要歐洲,還想要亞洲 如何修改
fun filterCountries(countries:List<Country>,continent: String):List<Country>{
val res = mutableListOf<Country>()
for (c in countries){
if(c.continent == continent){
res.add(c)
}
}
return res
}
需求變化二:不僅需要歐洲,亞洲等榜掌,還需要篩選一些 具有人口規(guī)模的國家
fun filterCountries(countries:List<Country>,continent: String,population: Int):List<Country>{
val res = mutableListOf<Country>()
for (c in countries){
if(c.continent == continent && c.population>=population){
res.add(c)
}
}
return res
}
如果按照現(xiàn)有的修改优妙,更多的篩選條件會作為方法參數(shù)不斷的累加,而且業(yè)務(wù)邏輯高度耦合憎账,解決問題的核心在于對這個方法進行解耦合 java做法:傳入一個類對象套硼,根據(jù)不同的篩選需求創(chuàng)建不同的子類(貌似就是策略模式),對于 kotlin 胞皱,支持高階函數(shù)邪意,我們可以把篩選邏輯變成一個方法傳入,這種思路更加簡單
為了了解高級特性反砌,所以假如有一個新的測試類
class CountryTest{
fun isBigEuropeanCountry(country: Country):Boolean{
return country.continent == "EU" && country.population>1000000
}
}
調(diào)用isBigEuropeanCountry 方法就能夠判斷一個國家是否是一個人口超過百萬的歐洲國家雾鬼,那么怎么才能把這個方法變成 filterCountries 方法的一個參數(shù)呢?要解決以下兩個問題
· 方法作為參數(shù)傳入宴树,必須像其他參數(shù)一樣具備具體的類型信息 (也就是說 需要一個函數(shù)類型)
· 需要把isBigEuropeanCountry 的方法引用當(dāng)作參數(shù) 傳遞給 filterCountries
函數(shù)的類型
格式:(Int)-> Unit
左邊是參數(shù)類型策菜,右邊是返回值類型
(a:Int,b:Int) ->Int
(Int ,String) ->String
(Int)->((Int) ->Unit) // 返回類型是一個函數(shù)也是可以的
有了函數(shù)類型,那么就可以修改filterCountries 方法了
fun filterCountries(countries:List<Country>,filter:(Country)->Boolean):List<Country>{
val res = mutableListOf<Country>()
for (c in countries){
if(filter(c)){
res.add(c)
}
}
return res
}
fun isBigEuropeanCountry(country: Country):Boolean{
return country.continent == "EU" && country.population>1000000
}
雖然已經(jīng)改造了篩選方法 但是我現(xiàn)在已經(jīng)有了一個篩選策略(isBigEuroupeanCountry)如何把這個函數(shù)傳進去呢酒贬? 也許你想這么用
filterCountries(countries, isBigEuropeanCountry) // 很遺憾這里第二個參數(shù)會報錯 又憨,原因是類型不匹配
//凌亂了,不是說 函數(shù)可以當(dāng)成參數(shù)來傳遞嗎锭吨,為啥這里不行
方法和成員引用
kotlin 存在一種特殊的語法蠢莺,通過兩個冒號來實現(xiàn)對于某個類的方法進行引用,假如我們有一個CountryTest類的實例對象 countryTest 零如,如果要引用他的isBigEuropeanCountry方法躏将,就可以這么寫
countryTest::isBigEuropeanCountry
在kotlin中函數(shù)是頭等公民,那么怎么直接對方法引用呢
::isBigEuropeanCountry
所以上面的使用我們就可以這樣寫
filterCountries(countries, ::isBigEuropeanCountry) // 這里不會再報錯
經(jīng)過這樣的重構(gòu)考蕾,程序顯然比之前優(yōu)雅許多祸憋,可以根據(jù)任意的篩選需求,調(diào)用同一個filterCountries 方法來獲取國家數(shù)據(jù)辕翰。
匿名函數(shù)
繼續(xù)思考一下篩選方法夺衍,每新增一個需求狈谊,就需要寫一個新的篩選方法喜命,但是很多都是零食性的,不需要被復(fù)用河劝,于是匿名函數(shù)就派上用場壁榕,kotlin支持在缺省函數(shù)名的情況下,直接定義一個函數(shù)赎瞎,所以isBigEuropeanCountry方法我們可以直接定義為
fun (country: Country):Boolean{ // 沒有函數(shù)名字
return country.continent == "EU" && country.population>100000
}
當(dāng)我們在編譯器里這么寫一個匿名函數(shù) 它會報錯 Function declaration must have a name 牌里,又凌亂了,不是可以定義匿名函數(shù)嗎,為啥又要有名字牡辽,可見 匿名函數(shù)不是這么用的
那這個匿名函數(shù)有啥用喳篇? 其實匿名函數(shù)是用來傳遞的,他的使用方式是這樣
filterCountries(countries, fun(country:Country):Boolean{ return country.continent == "EU" && country.population>100000})
知道了匿名函數(shù)态辛,我們再來看一下lambda是什么麸澜,由匿名函數(shù)可以知道,由于要傳入的匿名函數(shù) 早已經(jīng)在參數(shù)中聲明了參數(shù)和返回值奏黑,那么匿名函數(shù)中的參數(shù)和返回值是不是可以也不要了
fun filterCountries(countries:List<Country>?,filter:(Country)->Boolean):List<Country>{//這里可以推導(dǎo)出傳入的參數(shù)和返回的類型
val res = mutableListOf<Country>()
for (c in countries!!){
if(filter(c)){
res.add(c)
}
}
return res
}
fun test(){
filterCountries(countries, fun(country:Country):Boolean{ return country.continent == "EU" && country.population>100000})
filterCountries(null, {country -> country.continent == "EU" && country.population>100000 })
}
當(dāng)我們只保留需要的內(nèi)容 炊邦,就形成了lambda表達式 {參數(shù)變量 -> 返回值}
lambda 的表達是有自己的規(guī)則
- 一個lambda必須通過{} 來包裹
- 如果Lambda聲明了參數(shù)部分的類型,且返回值類型支持類型推導(dǎo)熟史,那么Lambda變量就可以省略函數(shù)類型聲明
val sum:(Int,Int) ->Int = {x:Int,y:Int ->x+y} // 完整寫法
val sum = {x:Int,y:Int -> x+y} // 推導(dǎo)類型寫法
val sum:(x:Int,y:Int)->Int = {x,y ->x+y} // 省略參數(shù)部分類型
- 如果Lambda 變量聲明了函數(shù)類型馁害,那么Lambda的參數(shù)部分類型就可以省略
- 此外 Lambda表達式返回不是Unit,那么默認最后一行表達式的值類型就是返回值類型
一個例子來看匿名函數(shù)蹂匹,Lambda究竟是什么
fun hello(int: Int) = { // 用Lambda初始化了一個函數(shù)
print(int)
}
hello(12) // 調(diào)用
上述例子會打印12 嗎 不會碘菜,因為foo(12)它不是函數(shù),也就是說 Lambda 不是函數(shù)限寞,他其實是一個對象炉媒,我們通過查看其Java代碼
private final Function0 hello(final int a) {
return (Function0)(new Function0() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke() {
this.invoke();
return Unit.INSTANCE;
}
public final void invoke() {
int var1 = a + TT2.INSTANCE.getCc();
boolean var2 = false;
System.out.print(var1);
}
});
}
通過把kotlin編譯成Java代碼發(fā)現(xiàn) hello這個方法里返回了一個Function0 類的匿名內(nèi)部類的對象,并且其內(nèi)部有invoke方法昆烁,那么我們想讓上述調(diào)用打印出12 應(yīng)該這樣寫
hello(12).invoke // 這個時候會打印出12
通過這個我們發(fā)現(xiàn) 匿名函數(shù)(簡寫 后成Lambda)都不是函數(shù)吊骤,而是對象,由于他們是對象静尼,所以能在函數(shù)中當(dāng)成參數(shù)傳遞白粉,這也就是高階函數(shù)的本質(zhì)
Function類型
kotlin 在JVM層設(shè)計了Function類型(Function0 Function1 ... Function22)來兼容Java的Lambda表達式,后綴數(shù)字代表了Lambda參數(shù)的數(shù)量鼠渺,每一個Function類型都有一個invoke方法
中綴表達式
kotlin中的中綴表達式有些什么鸭巴?
for (i in 1..10) print(i) //[1,10]
for( i in 1..10 step 2) println(i) // 1,3,5,7,9
for (i in 10 downTo 1) print(" $i") // 倒敘
for(i in 1 until 10) print(" $i") // [1,10)
val array:IntArray = IntArray(10)
for ((index,value) in array.withIndex()) // 數(shù)組index value 一起遍歷
以上這些奇特方法 如 in,step, downTo ,until 它們可以不通過點號拦盹,而是通過中綴表達式來被調(diào)用鹃祖,從而讓語法變得更加簡潔直觀。
自定義一個中綴表達式
class Person(val name:String,val age:Int)
infix fun Person.vs(person: Person){
when {
age - person.age >0 -> {
print("$name 比 ${person.name} 年長")
}
age == person.age -> {
print("$name 和 ${person.name} 一樣大")
}
else -> {
print("$name 比 ${person.name} 小")
}
}
}
val zhansan = Person("張三",18)
val lisi = Person("李四",19)
val wangwu = Person("王五",18)
zhansan vs lisi //張三 比 李四 小
如果把復(fù)雜條件寫成中綴的形式普舆,會讓代碼看起來特別簡潔
定義中綴函數(shù)的條件
- 中綴函數(shù)必須是某個類型的擴展函數(shù)或成員方法
- 中綴函數(shù)只能有一個參數(shù) (有且只有一個參數(shù))
中綴的形式 A 中綴方法 B(比如 張三 vs 李四)
由于to會返回Pair這種鍵值對的數(shù)據(jù)結(jié)構(gòu)恬口,因此我們經(jīng)常會與map結(jié)合一起使用
mapOf(1 to "one",2 to "two")
字符串
定義原生字符串 使用"""
val c = """ \nAAA """ // 這里的\n 不會轉(zhuǎn)義 定義html比較方便
val a ="A\nA" //
val c = "A${}B" // 字符串模版寫法 {表達式}