1. 程式人生 > 實用技巧 >Kotlin基礎學習(補)

Kotlin基礎學習(補)

寫在前面

在前文:Kotlin基礎學習中簡單學習了Kotlin的基本語法知識,但這些還沒有涉及到Kotlin中十分重要的Lambda程式設計以及空指標校驗等等特性。今天就把這部分寫一寫,但可能不會寫的很好,畢竟我自己的對這方面的理解還是欠缺。關於java裡的Lambda程式設計,我之前寫過兩篇部落格一部落格二,但寫的不是很詳細,需要的可以去看一看。

集合的建立與遍歷

介紹

要學習Lambda程式設計,集合的函式式API介面是入門Lambda的最佳案例,不過我們要先學習Kotlin中的集合。

集合,對於熟悉java的人來說,不用多說了。傳統意義上的集合主要指List和Set,如果廣泛點說的話Map這樣的鍵值對結構也可以說是集合。在java裡,List,Set,Map都是介面,我們一般都用他們的實現類,如ArrayList,HashSet,HashMap等等。

List

現在,為了入門集合,我們先提出這樣一個需求:建立一個包含多個水果名稱的集合。放到java裡,相信我們都會寫,kotlin中自然也能這麼寫:

val list = ArrayList<String>()
list.add("Apple")
list.add("Banana")
list.add("Orange")
list.add("Pear")
list.add("Grape")

但這種寫法未免太過於繁瑣了。Kotlin為此提供了一個內建的listOf()方法來簡化這種初始化的寫法:

val list = listOf("Apple", "Banana", "Orange", "Pear","Grape")

可以看到,我們只用了一行程式碼就完成了集合的初始化操作。

之前我們學習了for-in迴圈,for-in迴圈不僅可以遍歷區間,也可以遍歷集合,如下:

for (fruit in list) {
    println(fruit)
}

我們輸出,發現輸出了我們想要的結果:

不過這裡需要注意,listOf()函式建立的是一個不可變的集合,什麼叫不可變的集合呢?其實就是隻能讀,不能增刪改。我們從這裡也可以看出kotlin對於這些不可變性控制的十分嚴格。那我們怎麼建立一個可變的集合呢?使用mutableListOf()函式就可以了:

val list = mutableListOf("Apple", "Banana", "Orange", "Pear","Grape")
list.add("WaterMelon")
for (fruit in list) {
	println(fruit
}

這裡我們添加了一個新的元素西瓜,我們執行看看結果:

與我們預期的一致。

Set

而對於Set,其實和List的用法是幾乎一模一樣的,只是將用的函式變成了setOf()和mutableSetOf()而已,這裡就不再贅述了。

Map

我們在java中要建立一個Map要怎麼做呢?很簡單,new一個HashMap,定義好鍵和值的型別,然後挨個put就是了,在kotlin中也是一樣的:

val map = HashMap<String,Int>()
map.put("Apple",1)
...

但其實在kotlin中不建議我們使用put()和get()方法來對Map進行新增和讀取操作。而是更加推薦一種類似陣列下標的語法結構,如下:

map["Apple"] = 1
val number = map["Apple"]

如果學過php的話,會發現與php中的關聯陣列的用法似乎很類似呢。

當然,這種寫法仍然很麻煩。那麼kotlin中有我們上面用的listOf或者setOf()方法嗎?顯然是有的:

val map2 = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3)

這裡看上去是用to關鍵字進行關聯的,但實際上to並不是一個關鍵字,而是一個infix函式。這裡暫時不再細究了。

最後,我們要遍歷一個Map集合呢?當然也可以使用for-in了,如下:

    val map2 = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3)
    for ((fruit,number) in map2) {
        println("fruit is " + fruit + ", number is " + number)
    }

這裡與剛才的區別很明顯,我們直接將鍵和值分別取出來,賦值給了fruit和number,最後進行了一個列印輸出。如果學過ES6的語法的話,會發現也很類似。看來Kotlin吸取了很多指令碼語言的特性啊。

Lambda程式設計——集合的函式式API

入門介紹

想也知道,函式式API多的一批。這裡我們就簡單瞭解一些簡單的函式式API,主要是為了學習Lambda表示式的語法結構。

我們先提出一個需求:如何在一個水果集合裡面找到單詞最長的那個水果呢?要實現這個需求我們很自然的會想到這樣的一種寫法:對list集合進行遍歷,使用if語句挑選出長度最長的那個水果單詞即可。這段程式碼我就不在這裡寫了,相信各位都能十分容易地寫出這樣的程式碼。下面我們來看看使用kotlin的函數語言程式設計API寫法怎麼寫呢?如下:

val list = listOf("Apple", "Banana", "Orange", "Pear","Grape")
val maxLengthFruit = list.maxBy { it.length }

可以看到,我們一行程式碼就找到了最長的那個水果單詞,但可能還無法理解這段程式碼。下面我們來一點點的看。

Lambda程式設計的標準寫法

Lambda程式設計的定義,就是一小段可以作為引數傳遞的程式碼,這個一小段程式碼指的是什麼呢?kotlin並沒有限制,但我們使用時最好不要寫太長的程式碼,畢竟是引數嘛。

那麼我們直接來看Lambda表示式的語法結構:

{引數名1:引數型別, 引數名2:引數型別 -> 函式體}

首先,最外層是一對大括號,如果有引數傳入到Lambda表示式的話,我們就需要宣告引數列表,在引數列表的尾部加一個-> 然後在函式體裡寫我們需要寫的程式碼就好了。最後一行程式碼會自動作為Lambda表示式的返回值

當然,我們很多時候不會寫這麼標準的語法結構,大部分時候我們都使用的簡化形式。下面我們一步一步把程式碼簡化到入門介紹裡的那一行程式碼。

Lambda程式設計的簡化

仔細觀察入門介紹裡的一行程式碼:

val maxLengthFruit = list.maxBy { it.length }

這裡使用了一個maxBy函式,maxBy函式其實就是一個普通的函式,但需要傳入一個lambda表示式。maxBy的工作原理就是根據我們傳入的條件來遍歷集合,然後找到最大值。比如我們想找單詞最長的值,傳入單詞的長度即可。

理解了maxBy函式後,我們就可以按照上面的格式寫一個標準的Lambda表示式:

val list = listOf("Apple", "Banana", "Orange", "Pear","Grape")
val lambda = {fruit:String -> fruit.length}
val maxLengthFruit = list.maxBy(lambda)

這段程式碼很好懂吧?我們定義了一個fruit變數,然後把他的長度返回到了lambda變數,將其作為引數傳給了maxBy函式。接下來我們開始簡化。

  1. 顯而易見的,我們並不需要單獨定義一個lambda變數:

    val maxLengthFruit = list.maxBy({fruit:String -> fruit.length})
    
  2. Kotlin規定,當Lambda引數是函式的最後一個引數時,可以把Lambda表示式放到括號外面:

    val maxLengthFruit = list.maxBy(){fruit:String -> fruit.length}
    
  3. 接下來,如果Lambda引數是函式的唯一一個引數的話,還可以省略括號:

    val maxLengthFruit = list.maxBy{fruit:String -> fruit.length}
    
  4. 再然後,我們知道kotlin有優秀的型別推導機制,這就使得我們的Lambda表示式的引數列表大多數情況下不必宣告引數型別:

    val maxLengthFruit = list.maxBy{fruit -> fruit.length}
    
  5. 最後,當Lambda表示式只有一個引數時,也不必宣告引數名,直接用it關鍵字來代替即可:

    val maxLengthFruit = list.maxBy{it.length}
    

可以看到,我們一步一步簡化到了入門介紹裡的一行程式碼。

常用的函式式API

學習完了Lambda表示式,我們就學幾個最常用的函式式API來鞏固一下吧。

首先來學習map函式。集合中的map函式是一種最常用的函式式API,可以把集合中的元素對映成另外的值,最終形成新的集合。比如我們希望將所有的水果名變成大寫:

val list = listOf("Apple", "Banana", "Orange", "Pear","Grape")
val newList = list.map { it.toUpperCase() }

map函式十分強大,可以做很多事。接下來我們學習另一個函式式API——filter函式,filter函式聽名字就知道了,是用來過濾集合中的元素的,可以單獨使用也可以配合map函式。

比如我想保留五個5個字母以內的水果,且都變成大寫:

val list = listOf("Apple", "Banana", "Orange", "Pear","Grape")
val newList = list.filter { it.length <= 5 }
				.map { it.toUpperCase() }

最後,我們學習一下any和all函式,any用於判斷集合中是否至少存在一個元素滿足條件,all表示判斷集合中是否所有元素都滿足條件:

val anyResult = list.any{it.length <= 5}
val allResult = list.all{it.length <= 5}

其中的list指上文的list。返回值是布林型別的true或false。

Lambda程式設計——Java函式式API的使用

介紹

剛才我們學習了很多,但好像和安卓開發都沒有什麼關係。接下來我們學習一下Java函式式API的使用,學習了這個特性後會極大的便利我們的安卓開發。

kotlin實際上也可以使用函式式API,但有一定的條件限制:如果我們在Koltin中呼叫了一個java方法,並且該方法接收一個Java單抽象方法介面引數,就可以使用函式式API。Java單抽象方法介面指的是隻有一個待實現的方法。

從Runnable介面入門

聽起來似乎很抽象,接下來我們還是通過程式碼學習:

Thread(object:Runnable{
    override fun run(){
        println("Thread is running")
    }
}).start()

由於Kotlin中沒有new關鍵字,固建立匿名內部類就不能再使用new了,改用了object關鍵字。這樣寫雖然不是很複雜,但好像跟java的匿名類寫法沒啥區別。

但Thread類的構造方法此時就符合Java函式式API的使用條件,我們就直接對程式碼進行精簡:

Thread(Runnable{
    println("Thread is running")
})

可以看到,已經很方便了。但這裡提出一個特性——如果一個Java方法的引數列表不存在一個以上Java單抽象方法介面引數,就可以將介面名進行省略:

Thread{
    println("Thread is running")
}.start()

這樣,程式碼就十分簡單了。可這和我們做安卓開發有什麼關係呢?要知道Android SDK裡面還是使用Java編寫的,有很多介面都具有這種特性。比如我們最常用的按鈕的點選事件:

button1.setOnClickListener {}

就可以直接這麼寫了,還是方便了很多的。

總結

本來想一篇部落格將Lambda程式設計和空指標檢查直接寫完了的,但好像內容有點太多了,只寫完了Lambda程式設計的部分。今天就先寫到這裡吧,關於空指標檢查的部分明天再寫。