kotlin 學習教程(五) |函數和函數式編程

1. 前言

我們知道,在程序中,通常情況下。一個類會有自己的方法(函數)以及屬性,這些方法代表了該類的特性或者說具有的能力。今天我們一起來研究一下 kotlin中的函數。

2.如何聲明一個函數

在 kotlin 中,我們通過關鍵字 fun 來聲明一個函數

fun multiply(x:Int,y:Int):Int{
   ...  //代碼塊
    return x*y
}

如上面的代碼所示:我們定義了函數multiply()并指定其返回類型為 Int 類型。

3.Lambda表達式

3.1 Lambda表達式介紹

從Java8 開始,Lambda表達式在 Lambda表達式,在其他的編程語言中(例如:Scala語言),這種表達式是語法糖中的一種,在 kotlin 中,也支持這種語法,它允許把函數作為一個方法的參數,可以使代碼變的更加簡潔緊湊。

Lambda表達式的本質其實是匿名函數,因為在其底層實現中還是通過匿名函數來實現的。但是我們在用的時候不必關心起底層實現。不過Lambda的出現確實是減少了代碼量的編寫,同時也是代碼變得更加簡潔明了。

3.2 Java 8 Lambda 表達式
3.2.1 語法:
(parameters) -> expression
或
(parameters) ->{ statements; }
3.2.2 特征
  • 可選類型聲明:不需要聲明參數類型,編譯器可以統一識別參數值。

  • 可選的參數圓括號:一個參數無需定義圓括號,但多個參數需要定義圓括號。

  • 可選的大括號:如果主體包含了一個語句,就不需要使用大括號。

  • 可選的返回關鍵字:如果主體只有一個表達式返回值則編譯器會自動返回值,大括號需要指定明表達式返回了一個數值。

3.2.3 代碼如下:
// 1. 不需要參數,返回值為 5  
() -> 5  
  
// 2. 接收一個參數(數字類型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2個參數(數字),并返回他們的差值  
(x, y) -> x – y  
  
// 4. 接收2個int型整數,返回他們的和  
(int x, int y) -> x + y  
  
// 5. 接受一個 string 對象,并在控制臺打印,不返回任何值(看起來像是返回void)  
(String s) -> System.out.print(s)

從上面可以看出,代碼簡潔了不少。

我們再來看一個例子,在java中假如要計算兩個數的和,則代碼如下:

import java.util.function.BiFunction;

public class helloword {
    public static void main(String[] args) {
        BiFunction<Integer,Integer,Integer> sum=(Integer x, Integer y) ->{
            return x+y;
        };
        System.out.println("sum(3, 4) = " + sum.apply(3, 4));
    }

}

運行結果如下:


Snipaste_2020-02-17_10-31-55.png

但是,在 java 8 以下版本,并不支持 Lambda 表達式,而這個時候.kotlin 完美的兼容了 java 8 以下版本對 lamdba 表達式的支持,并且能夠進行混合開發。

3.3 kotlin Lamdba 表達式

好,我們再來看看 Android 中 kotlin 的使用,Kotlin語言中

3.3.1 Lamdab 表達式 的聲明:

lambda表達式的完整語法如下:

{ params -> expressions }
  • params表示參數列表,expressions表示具體實現,可以是單行語句,也可以是多行語句。

  • Lamdba 表達式的值為大括號最后一行的值。

3.3,2 Lamdba 表達式的類型表示:
  • () ->unit 無參,返回值為Unit

  • (Int)-> Int 傳入整型,返回一個整型

  • (String,(String)->String)->Boolean 傳入字符串,Lamdba 表達式,返回 Boolean

3.3.3 Lamdba 表達式的調用:
  • 使用 () 進行調用,相當于invoke()
3.3.4 Lamdba 表達式的簡化:
args.forEach(){ println(it) }  // 如果函數的最后一個參數是lamdba 表達式,則可以將lamdba 放在括號外面
args.forEach{ println(it) }  // 如果函數參數只有一個lamdba表達式,則調用時小括號可以省略。
args.forEach(::print)    //入參,返回值與形參一致的函數可以用函數引用的方式作為實參傳入。

上述同樣的功能,kotlin 語言的實現如下:

  • 自定義函數來實現:
fun main(args: Array<String>) {
  val result=sum(3,4)
    println(result)
}
  fun sum(arg1:Int,arg2:Int)=arg1+arg2
  • 使用 Lamdba 表達式實現:
fun main(args: Array<String>) {
    println(result(3, 4))
}

val result = { arg1: Int, arg2: Int ->
    println("$arg1+$arg2=${arg1 + arg2}")
    arg1 + arg2
}

我們再來看個例子,要求:遍歷kotlin 中main函數的參數,

  • 示例代碼:Android 中最常見的點擊事件
tv_toLogin.setOnClickListener(object:View.OnClickListener{
            override fun onClick(v: View?) {
                Toast.makeText(this,"onClick",Toast.LENGTH_SHORT).show() 
            }

        })
  • 使用 Lambda 表達式的點擊事件
tv_toLogin.setOnClickListener({
            Toast.makeText(this,"onClick",Toast.LENGTH_SHORT).show() 
        })

怎么樣?有沒有感覺 lamdba 表達式使用起來既簡潔又優雅。

4. 高階函數

4.1 什么是高階函數

高階函數就是以另一個函數作為參數或返回值的函數,Kotlin 可以以 lambda 或參數引用作為參數或返回值,所以,任何以 lambda 或函數引用作為參數或返回值的都是高階函數。

4.2 常見的高階函數
4.2.1 forEach()函數
fun main(args: Array<String>) {
   val list= listOf(1,2,3,4,5,6,7,8)
    val newList=ArrayList<Int>()
    list.forEach{
        val newElement=it *2+3
        newList.add(newElement)
    }
    newList.forEach(::println)
}

運行結果如下:

Snipaste_2020-02-17_10-31-55.png
4.2.2 :map()函數

map: 接受一個lambda表達式,并且有返回值,形成一個新的list,實現對集合中的元素進行修改

好,我們通過高階函數 map 來實現如上的效果:

fun main(args: Array<String>) {
   val list= listOf(1,2,3,4,5,6,7,8)
   val newList=list.map {
       it*2+3
   }
    println(newList)
}

運行結果如下:

Snipaste_2020-02-17_10-31-55.png

以上代碼中通過高階函數 map 實現了對集合中每個元素乘以2再加3的操作,不用去遍歷集合中每個元素,是不是簡單了許多。

4.2.3 flatMap()函數

flatMap是map和flat兩個函數的“復合邏輯,可以將集合中的數據進行合并成一個集合。

示例代碼如下:

fun main(args: Array<String>) {
  val list= listOf(
      1..20,
      21..30,
      31..100
  )
  
   val flatList=list.flatMap {
       it.map {
           "NO.$it"
       }
   }

    flatList.forEach{
        println(it)
    }
}

運行結果如下: 將集合中數字從1~100打印輸出,每個數字前標有“NO.”


Snipaste_2020-02-17_10-31-55.png
4.2.4 filter()函數

傳入Lambda 表達式為 true 是,保留該元素;使用filter對集合進行按條件過濾

fun main(args: Array<String>) {
    val list= listOf(1,2,3,4,5,6,7,8,9)
    val result=list.filter {
        it%2==0
    }
    println(result)
}

運行結果如下:


Snipaste_2020-02-17_10-31-55.png
4.3 kotlin 中的 特殊函數
4.3.1 run()函數

該函數實際上可以說是let和with兩個函數的結合體,run函數接收一個lambda函數為參數,以閉包形式返回,返回值為最后一行的值或者指定的return的表達式。

示例代碼如下:

fun main(args: Array<String>) {
    FunKotlin().myFun()
    run {
        FunKotlin().myFun()
    }
    run {
        println("kotlin")
    }
}

class FunKotlin{

    fun myFun():String{
        println("開始執行myFun()函數")
        return "kotlin 中的特殊函數"
    }
}

運行結果如下:

Snipaste_2020-02-17_10-31-55.png

在上面的代碼中,我們定義了myFun()函數并通過run() 進行調用,調用的結果即為myFun()的結果。

run() 源碼如下:

/**
 * Calls the specified function [block] and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

如代碼所示:我們傳入block()參數,最終返回了block() 的執行結果。

4.3.2 apply()函數

源碼如下:

/**
 * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
 */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

如代碼所示:我們傳入 block() 函數,先是調用了block()函數,然后返回當前的調用者對象this,也就是說先執行完block()代碼塊邏輯后,再次返回當前的調用者對象。

示例代碼如下:

fun main(args: Array<String>) {
    testApply()
}

fun testApply(){
    val list= mutableListOf<String>()
    list.add("A")
    list.add("B")
    list.add("C")
    list.add("D")
    list.add("E")
    println("普通寫法:list=${list}")

    val applyList=mutableListOf<String>().apply {
        add("A")
        add("B")
        add("C")
        add("D")
        add("E")
        println("使用apply 函數寫法 this=${this}")
    }
}

運行結果如下:


Snipaste_2020-02-17_10-31-55.png

如代碼所示:我們的需求是創建一個集合并向其中添加元素"A",“B”,"C",“D”,“E”,然后打印出該集合,相比普通寫法,使用apply() 函數顯然簡潔了許多。

4.3.3 let() 函數

let擴展函數的實際上是一個作用域函數,當你需要去定義一個變量在一個特定的作用域范圍內,let函數的是一個不錯的選擇;let函數另一個作用就是可以避免寫一些判斷null的操作

源碼如下:

/**
 * Calls the specified function [block] with `this` value as its argument and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

如代碼所示,我們重點看最后一行return block(this),就是說把當前調用對象作為參數傳入block()代碼塊中。

示例代碼如下:我們以Android中在適配器 adapter 中進行網絡圖片的加載。

context?.let {         
  Glide.with(it).load(item.envelopePic).crossFade().into(helper.getView<ImageView>(R.id.iv_envelopePic))
                }

在上面的代碼中,it 指代的即是 context,意為上下文。</pre>

4.3.4 also()函數

源碼如下:

/**
 * Calls the specified function [block] with `this` value as its argument and returns `this` value.
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

看最后兩行代碼,先是執行了block(this),隨后返回this,即當前的調用者對象。

示例代碼如下:

fun testAlsoFun() {
    val a = "ABC".also {
        println(it) //輸出:ABC
    }
    println(a) //輸出:ABC
    a.let {
        println(it) //輸出:ABC
    }
}
fun main(args: Array<String>) {
    testAlsoFun()
}

在上面的代碼中,字符串“ABC”調用了also(),會打印出調用者 “ABC”.

4.3.5 with() 函數

源碼如下:

/**
 * Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

我們看到with()函數傳入了一個接收者對象receiver,然后使用該對 象receiver去調用傳入的Lambda代碼塊receiver.block()。

  • 示例代碼在 Android中我們初始化一個控件并給其賦值,Java 語言實現如下:
TextView text=(TextView)findViewById(R.id.tv_text)
text.setText("哈哈哈")
text.setTextSize(23)
  • kotlin 語言實現如下:
with(tv_text){
text="哈哈哈"
textSize=23
}

在上面的代碼中,實現的功能是一樣的,但是顯然 kotlin 語言更加的簡潔。

5.擴展函數與屬性

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容