Kotlin語法梳理(二)

Kotlin語法梳理(一)
目錄

函式
Kotlin中的函式很靈活,它可以獨立於類或介面之外存在,即 頂層函式 ,也就是 全域性函式 ;也可以存在於別的函式中,即 區域性函式 ;還可以存在於類或介面之中,即 成員函式 。
fun 函式名(引數列表):返回值型別{
函式體
return 返回值
}
fun add(n1:Int,n2:Int) :Int{ val sum = n1 + n2 return sum }
有的函式沒有返回值,此時可以將函式的返回值型別宣告為 Unit ,相當於Java的void;或者可以省略不寫。
Nothing型別
Kotlin中提供一種特殊的資料型別 Nothing , Nothing 只用於函式返回型別宣告,不能用於變數宣告。 Nothing 宣告的函式永遠不會正常的返回,只會丟擲異常。
fun read(): Nothing { throwIOException() }
Nothing意思在於,有些框架,例如Junit單元測試框架,在測試失敗時會呼叫Nothing返回型別的函式,通過它丟擲異常使當前測試用例失敗。
採用命名引數呼叫函式
fun getArea(width:Int,height:Int):Int{ return width * height } getArea(width = 3,height = 4) 或 getArea(height = 4,width = 3)
可見 採用命名引數呼叫函式 ,呼叫者能夠清晰地看出傳遞引數的含義,命名引數對於有多引數函式呼叫非常有用。另外,採用命名引數函式呼叫時, 引數順序可以與函式定義時引數順序不同 。
注意在呼叫函式時,一旦其中一個引數採用了命名引數形式傳遞,那麼其後的所有引數都必須採用命名引數形式傳遞,除非它是最後一個引數。
引數預設值
在宣告函式的時候可以為引數設定一個預設值,當呼叫函式的時候可以忽略該引數。
fun getArea(width:Int,height:Int = 4):Int{ return width * height } getArea(3)// 等價於getArea(3,4)
採用引數預設的函式作用,類似java中的方法過載,Kotlin中提倡使用預設值的方式,因為引數預設值只需要宣告一個函式就可以,而過載則需要宣告多個函式。
可變引數
可以通過在引數名前面加 vararg關鍵字 的方式來表示這是可變引數。
fun sum(vararg numbers:Int) :Int{ var sum = 0 for(number in numbers){ sum += number } return sum } sum(1,2) sum(1,2,3,4) sum(1,2,3,4,5,6,7)
注意:可變引數不是最後一個引數時,後面的引數需要採用命名引數形式傳遞。
展開運算子“ * ”
如果已經有一個數組變數,能否傳遞給可變引數呢?這需要使用 展開運算子 *
val ary = intArrayOf(1,2,3,4) sum(*ary)
表示式函式體
如果在函式體中表達式能夠表示成單個表示式時,那麼函式可以採用更加簡單的表示方式。
fun add(n1:Int,n2:Int) :Int{ val sum = n1 + n2 return sum } fun add(n1:Int,n2:Int) :Int = n1 + n2
區域性函式
宣告在一個函式中的函式就叫做區域性函式
fun calculate(n1: Int, n2: Int, opr: Char): Int { //區域性函式 fun add(a: Int, b: Int): Int = a + b fun sub(a: Int, b: Int): Int = a - b return if (opr == '+') add(n1, n2) else sub(n1, n2) }
內部函式的作用於是在外函式體內,如果直接訪問 區域性函式 ,會發生 編譯錯誤 。
函數語言程式設計
函數語言程式設計(functional programming)是一種程式設計典範,也就是面向函式的程式設計。在函數語言程式設計中一切都是函式。
函數語言程式設計核心概念如下:
- 函式是“一等公民”: 是指函式與其他資料型別是一樣的,處於平等的地位。函式可以作為其他函式的引數傳入,也可以作為其他函式的返回值返回。
- 使用表示式,不用語句: 函數語言程式設計關心的輸入和輸出,即:引數和返回值。在程式中使用表示式可以有返回值,而語句沒有。例如:控制結構中的if和when結構都屬於表示式。
- 高階函式: 函數語言程式設計支援高階函式,所謂高階函式就是一個函式可以作為另外一個函式的引數或返回值。
- 無副作用: 是指函式執行過程會返回一個結果,不會修改外部變數,這就是“純函式”,同樣的輸入引數一定會有同樣的輸出結果。
Kotlin語言支援函數語言程式設計,提供了函式型別、高階函式和Lambda表示式。
函式型別
Kotlin中每一個函式都有一個型別,稱為 “函式型別” 。函式型別作為一種資料型別與其他資料型別在使用場景上沒有區別。可以宣告變數,也可以作為其他函式的引數或者其他函式的返回值使用。
//該函式的函式型別為:(Int,Int) -> Int fun rectangleArea(width:Int,height:Int):Int{ return width * height } //宣告一個變數,型別為:(Int,Int) -> Int 賦值為rectangleArea val getArea :(Int,Int) -> Int = ::rectangleArea //呼叫函式 val area = getArea(3,4)
Kotlin 中 雙冒號操作符 表示把一個方法當做一個引數,傳遞到另一個方法中或者變數進行使用,通俗的來講就是引用一個方法。
函式型別就是把函式引數列表中的引數型別保留下來,再加上箭頭符號和返回型別,形式如下:
**引數列表中的引數型別 -> 返回型別 **
(Int,Double) -> Double
每一個函式都有函式型別,即便是函式列表中沒有引數,以及沒有返回值的函式也有函式型別( ()->Unit )。
函式字面量
函式型別可以宣告變數,那麼函式型別變數能夠接收什麼的資料呢?即函式字面量如何表示?, 函式字面量可以有三種表示:
- 函式引用: 引用到一個已經定義好的,有名字的函式。它可以作為函式字面量。
- 匿名函式: 沒有名字的函式,即匿名函式,它也可以作為函式字面量。
- Lambda表示式: Lambda表示式是一種匿名函式,可以作為函式字面量。
fun calculate(opr:Char):(Int,Int) -> Int{ fun add(a:Int,b:Int):Int = a + b //加法函式 fun sub(a:Int,b:Int):Int = a - b //減法函式 val result :(Int,Int) -> Int = when(opr){ '+' -> ::add//函式引用 '-' -> ::sub//函式引用 '*' -> { fun(a:Int,b:Int) :Int = a * b//匿名函式 } else ->{ a,b ->//Lambda表示式 a / b } } return result }
高階函式
可以把函式作為另一個函式的返回值或者引數使用,那麼這個函式就屬於 高階函式 。
上述calculate函式就是一個高階函式,返回值為 (Int,Int) -> Int
。
函式作為引數使用
//funcName引數是函式型別 fun getAreaByFunc(funcName:(Int,Int) -> Int,width:Int,height:Int) :Int{ return funcName(width,height) } //呼叫 var result = getAreaByFunc(::rectangleArea,3,4)
Lambda表示式
Lambda表示式是一種匿名函式,可以作為表示式、函式引數和函式返回值使用, Lambda表示式 的運算結果是一個函式。
語法格式
Kotlin中的 Lambda表示式 很靈活,其標準語法格式如下:
{ 引數列表 -> Lambda體 }
Lambda表示式的引數列表與函式的引數列表形式類似,但是Lambda表示式引數列表前後沒有小括號。箭頭符號將引數列表與Lambda體分隔開,Lambda表示式不需要宣告返回型別。Lambda表示式可以有返回值, 如果沒有return語句Lambda體的最後一個表示式就是Lambda表示式的返回值,如果有return語句返回值是return語句後面的表示式。
注意: Lambda表示式與函式 ,匿名函式一樣都有 函式型別 ,但從Lambda表示式的定義中只能看到引數型別,看不到返回值型別,但是返回值型別可以通過上下文推匯出來。
Lambda表示式也是函式型別,那麼就可以宣告變數,也可以作為其他函式的引數或者返回值使用。
//funcName引數是函式型別 fun getAreaByFunc(funcName: (Int, Int) -> Int, width: Int, height: Int): Int { return funcName(width, height) } //Lambda表示式作為函式引數使用 val result = getAreaByFunc({ w, h -> w * h }, 3, 4)
Lambda表示式簡化寫法
Kotlin提供了多種 Lambda表示式簡化寫法 ,下面介紹其中幾種。
- 引數型別推導簡化: 型別推導是Kotlin的強項,Kotlin編譯器可以根據上下文環境推匯出引數型別和返回值型別。
以下程式碼是標準形式的Lambda表示式: { a: Int, b: Int -> a + b } Kotlin能推匯出引數a和b是Int型別,當然返回值也是Int型別。簡化形式如下: { a, b -> a + b }
- 使用尾隨Lambda表示式: Lambda表示式可以作為函式的引數傳遞,如果Lambda表示式很長,就會影響程式的可讀性。如果一個函式的最後一個引數是 Lambda表示式 ,那麼這個Lambda表示式可以放在 函式括號之後 。示例程式碼如下:
fun getAreaByFunc(width: Int, height: Int, funcName: (Int, Int) -> Int): Int { return funcName(width, height) } //尾隨Lambda表示式 val result = getAreaByFunc(3, 4) { w, h -> w * h }
如果沒有其他的引數,可以省略括號
fun printFunc(funcName: (Int, Int) -> Int) { println("${funcName(1, 2)}") } //呼叫printFunc方法,只有一個引數,型別是函式型別,傳遞的是Lambda表示式,括號可以省略 printFunc { w, h -> w * h }
注意:尾隨Lambda表示式容易被誤認為是函式宣告,但它不是,而是函式呼叫
- 省略引數說明: 如果Lambda表示式的引數只有一個,並且能夠根據上下文環境推匯出它的資料型別,那麼這個引數宣告可以省略,在Lambda體中使用隱士引數 it 替代Lambda表示式的引數,示例程式碼如下:
fun printFunc(str:String,funcName: (String) -> String) { println(funcName(str)) } printFunc("省略引數",{ it.reversed()//隱士引數 it })
Lambda體中 it隱式變數 是由Kotlin編譯器生成的,它的使用有 兩個前提:一是Lambda表示式只有一個引數,二是根據上下文能夠推匯出引數型別。
閉包
閉包(closure)是一種特殊的函式,它可以訪問函式體之外的變數,這個變數和函式一同存在,即使已經離開了它的原始作用域也不例外。這種特殊函式一般是區域性函式、匿名函式或Lambda表示式。
閉包可以訪問函式體之外的變數,這個過程稱為 捕獲變數
//全域性變數 var value = 10 fun main(args: Array<String>) { //區域性變數 var localValue = 20 val result = {a:Int -> value++ localValue++ val c = a + value + localValue println(c) } result(30)//輸出62 println("localValue = $localValue")//輸出21 println("value = $value")//輸出11 }
Java中 Lambda表示式 捕獲區域性變數時,區域性變數只能是 final 的。在 Lambda 體中只能讀取區域性變數,不能修改區域性變數。而 Kotlin 中沒有這個限制,可以讀取和修改區域性變數。
閉包捕獲變數後,這些變數被儲存在一個特殊的容器中被儲存起來。即便是宣告這些變數的原始作用域已經不存在,閉包體中仍然可以訪問這些變數。
行內函數
在 高階函式 中引數如果是函式型別,則可以接收Lambda表示式,而Lambda表示式在編譯時被編譯稱為一個匿名類,每次呼叫函式時都會建立一個物件,如果這種被函式反覆呼叫則建立很多物件,會帶來執行時額外開銷。為了解決次問題,在Kotlin中可以將這種函式宣告為 行內函數 。
行內函數在編譯時不會生成函式呼叫程式碼,而是用函式體中實際程式碼替換每次呼叫函式。
自定義行內函數
Kotlin標準庫提供了很多常用的 行內函數 ,開發人員可以自定義行內函數,但是 如果函式引數不是函式型別,不能接收Lambda表示式,那麼這種函式一般不宣告為行內函數 。宣告行內函數需要使用關鍵字 inline修飾 。
//引數型別有函式型別,宣告為行內函數 inline fun printFunc(str:String,funcName: (String) -> String) { println(funcName(str)) }
let函式
在 Kotlin 中任何物件都可以一個 let函式 , let函式 後面尾隨一個 Lambda表示式 ,在物件非空時執行Lambda表示式中的程式碼,為空時則不執行。
a?.let { print(a) //a不為空時輸出,為空時不執行 }
with和apply函式
有時候需要對一個物件設定多個屬性,或呼叫多個函式時,可以使用 with或apply函式 。與let函式類似Kotlin中所有物件都可以使用這兩個函式。
在自定義view中當我們初始化畫筆時很多時候我們會寫下邊的程式碼
var paint = Paint() paint.color = Color.BLACK paint.strokeWidth = 1.0f paint.textSize = 18.0f paint.isAntiAlias = true
如果使用with,那麼就可以寫成這樣
var paint = Paint() with(paint) { color = Color.BLACK strokeWidth = 1.0f textSize = 18.0f isAntiAlias = true }
apply函式與 with函式 類似, apply函式 是有返回值的,它的返回值就是當前物件。 如果不需要返回值可以使用 with函式
關注微信公眾號獲取Kotlin視訊和書籍資料

Android行動派