天天看點

Kotlin學習筆記(二)

匿名函數

我們在定義函數時,不給他名字,這個函數就是匿名函數了。匿名函數通常用法是作為參數傳遞給其他函數或者被其他函數傳回。

Kotlin因有了匿名函數,我們開發者就可以輕松實作對kotlin标準庫内置函數的定制。案例:

fun main() {
    val a = "luffy".count()
    var b = "luffy love honey".count({ letter -> letter.equals('y') })
    println("a == " + a)
    println("b == " + b)
}           

匿名函數也是有類型,類型由傳入的參數和傳回值決定。匿名函數可以當作變量指派給函數類型變量,就像其他變量一樣,匿名函數也可以在代碼中傳遞。和具名函數一樣,匿名函數可以是無參數或接受一個或多個不同類型的參數。

fun main() {
    //先定義匿名函數變量,noNameval是變量名,() -> String 表示該變量是無參數且傳回值類型為String的匿名函數類型。
    val noNameval: () -> String
    //再指派
    noNameval = {
        val holiday = "Mid-Autumn Festival"
        "Happy $holiday"
    }

    //直接指派
    val noNameval1: () -> String = {
        val holiday = "Mid-Autumn Festival"
        "Happy $holiday"
    }
    println(noNameval())
    println(noNameval1())
}           

有小夥伴可能注意到了,上面案例中,匿名函數傳回值類型是String,但是我并沒有用return傳回字元串,其實原因是匿名函數一般情況下會隐式傳回最後一個語句的執行結果,而不需要我們喲用return關鍵字顯式傳回資料。

帶參數的匿名函數

參數的類型放在匿名函數的類型定義中,參數名則放在函數定義中:

//帶一個參數的匿名函數
    val noNameval2: (String) -> String = { name ->
        val holiday = "Mid-Autumn Festival"
        "$name,Happy $holiday"
    }           

當匿名函數隻要一個參數時,可以使用it關鍵字表示參數名:

//用it關鍵字改造
    val noNameval3: (String) -> String = {
        val holiday = "Mid-Autumn Festival"
        "$it,Happy $holiday"
    }           

類型推斷

定義一個變量時,如果把匿名函數作為變量指派給它,不需要顯式指明變量類型,kotlin支援類型推斷。

val noNameval4 = {
        val holiday = "Mid-Autumn Festival"
        "Happy $holiday"
    }           

類型推斷也支援帶參數的匿名函數,但為了幫助編譯器更準确地推斷變量類型,必須标明匿名函數的參數名和參數類型。

val noNameVal5 = { name: String, year: Int ->
        val holiday = "Mid-Autumn Festival"
        "$name,Happy $holiday,$year"
    }           
我們将匿名函數稱為lambda,将它的定義稱為lambda表達式,它傳回的資料稱為lambda結果。為什麼叫lambda?lambda也可以用希臘字元 λ 表示,是lambda演算的簡稱,lambda是一套數理演算邏輯,是由數學家Alonzo Church(阿隆佐.丘齊)于20世紀30年代發明,在定義匿名函數時,使用了lambda演算記法。

定義參數是函數的函數

即函數的參數是另外一個函數。

fun main() {
    val discountWords = { goodsName: String, hours: Int ->
        val currentYear = 2022
        //給變量加上大括号{}之後,字元串就沒有空格了
        "${currentYear}年,${goodsName}雙11大促銷倒計時還有:$hours 小時"
    }

    getDiscountWords("牙膏", discountWords)
}

fun getDiscountWords(goodsName: String, discountWords: (String, Int) -> String) {
    //傳回一個1到24的随機數
    val hour = (1..24).shuffled().last()
    println(discountWords(goodsName, hour))
}
           

簡略寫法

如果一個函數的lambda排在最後,或者是唯一的參數,那麼lambda外面的圓括号可以省略掉。

fun main() {
    //如果一個函數的lambda排在最後,或者是唯一的參數,那麼lambda外面的圓括号可以省略掉
    val a = "luffy".count { it == 'f' }
    println("a = $a")
    getDiscountWords("牙膏") { goodsName: String, hours: Int ->
        val currentYear = 2022
        "${currentYear}年,${goodsName}雙11大促銷倒計時還有:$hours 小時"
    }
}

//具名函數
private fun getDiscountWords(goodsName: String, discountWords: (String, Int) -> String) {
    //傳回1到24的随機數
    val hour = (1..24).shuffled().last()
    println(discountWords(goodsName, hour))
}           

函數内聯

lambda可以讓你更靈活地編寫應用,但是靈活也是要付出代價的。在JVM上,我們定義的lambda會以對象執行個體的形式存在,JVM會為所有同lambda打交道的變量配置設定記憶體,這就産生了記憶體開銷,更糟的是,lambda的記憶體開銷會帶來嚴重的性能問題。幸運的是,kotlin有一種優化機制叫内聯。有了内聯,JVM就不需要使用lambda對象執行個體了,因而避免了變量記憶體配置設定。哪裡需要使用lambda,編譯器就會将函數體指派粘貼到哪裡,跟C語言中的宏定義比較類似,内聯關鍵字是inline。

使用lambda的遞歸函數無法内聯,因為會導緻複制粘貼無限循環,編譯器也會警告提示。

 我們show kotlin bytecode,再把它反編譯一下,就能看到lambda确實是以執行個體的形式存在:

Kotlin學習筆記(二)
Kotlin學習筆記(二)

 使用内聯之後,可以看到在有lambda的地方,JVM會幫我們複制粘貼lambda:

Kotlin學習筆記(二)
Kotlin學習筆記(二)

 函數引用

要把函數作為參數傳遞給其他函數使用,除了使用 lambda,kotlin還提供了函數引用的方式,函數引用可以把一個具名函數轉換成一個值參,使用lambda的地方都可以使用函數引用。

fun main() {
    showOnBoader("牙膏", ::getDiscountWords)
}

private fun showOnBoader(goodsName: String, discoutWords: (String, Int) -> String) {
    val hours = (1..24).shuffled().last()
    println(discoutWords(goodsName, hours))
}

private fun getDiscountWords(goodsName: String, hours: Int): String {
    val currentYear = 2020
    return "${currentYear}年雙十一${goodsName}大促銷倒計時:$hours 小時"
}           

 函數類型作為傳回類型

把函數類型作為一個函數的傳回類型:

fun main() {
    val discountWords = configDiscountWords()("洗衣液")
    println(discountWords)

    val discountWords1 = configDiscountWords()
    println(discountWords1("洗衣液"))
}

fun configDiscountWords(): (String) -> String {
    val currentYear = 2020
    val hours = (1..24).shuffled().last()
    return { "${currentYear}年雙十一${it}大促銷倒計時:$hours 小時" }
}           

閉包

fun configDiscountWords(): (String) -> String {
    var currentYear = 2020
    val hours = (1..24).shuffled().last()
    return {
        //在kotlin中,匿名函數能夠修改并引用定義在自身作用域之外的變量,即匿名函數可以引用定義自身的函數裡面的變量
        currentYear += 10
        "${currentYear}年雙十一${it}大促銷倒計時:$hours 小時"
    }
}           

lambda與匿名内部類

import java.security.SecureRandom;

/**
 * @author luffy
 * @description Java中的匿名内部類
 */
public class JavaAnonymousClass {

    public static void main(String[] args) {
        showOnBoard("農夫山泉", (goodsName, hour) -> {
            int currentYear = 2022;
            return String.format("%d年,雙11%s促銷倒計時:%d 小時", currentYear, goodsName, hour);
        });
    }

    public interface DiscountWords {
        String getDiscountWords(String goodsName, int hour);
    }

    public static void showOnBoard(String goodsName, DiscountWords discountWords) {
        int hour = new SecureRandom().nextInt(24);
        System.out.println(discountWords.getDiscountWords(goodsName, hour));
    }

}