kotlin 基礎語法-02-函數-高階函數

kotlin 基礎語法-02-函數-高階函數

本篇文章 主要介紹kotlin 中的函數、嵌套函數、擴展函數、 lambda 語法 、 高階函數、內聯函數 體會函數式編程的能力。

1.函數

我們已經很熟悉函數的聲明瞭、
基本函數的聲明語法如下:

fun  funName(arg1:String="默認值"):返回值類型{
    /// 函數體 
}

其中參數後邊的默認值是可以省略的、 如果返回值爲空 ,在java中void 、 在kotlin 可以使用Unit來表示, 當然也是可以省略的、 默認返回值爲空 。

還有一個特性 就是如果函數體中只有一個語句的話、 那麼可以這樣來表示

fun sum(num1: Int , num2: Int ) = print("$num1 + $num2= " +( num1+ num2))

這裏不再贅述, 我們繼續。

2.函數嵌套

個人表示不建議、也不喜歡這樣的寫法、 在java中類似於內部類。
來一個demo 看一下

fun  demo(words:String){
    
    val  str = "hello kotlin"
    
    fun sayHello(i :Int = 10 ){
        println(str )
        
        if(i > 0){
            sayHello(i-1)
        }
        
    }
    
    sayHello()
}

打印結果就是:

hello 10
hello 9
hello 8
hello 7
hello 6
hello 5
hello 4
hello 3
hello 2
hello 1
hello 0

個人覺得這種坑逼的寫法, 由於比較隱蔽、 內部函數可以訪問外部函數變量、 而且外部的其他函數是無法調用改函數, 所以主要用途在於,某些條件下觸發遞歸的函數、或者說根本就不希望外部函數訪問到的函數。
當然個人寫法,我是不會這樣寫的, 更不推薦這麼寫, 搞得跟中介似的。 好了, 我們繼續。

3. 擴展函數的靜態解析

擴展函數主要用於第三方sdk 或者一些自己無法控制的類的、這時候如果想給這個類添加一些方法或者成員變量。 這時候就可以使用擴展函數 。
來個例子:

fun printFile(fileName:String){
    var file = File(fileName)
    println(file.readText()
}

這裏用的是系統內置的擴展函數、我們看一下 FileReadWrite.kt

/**
 * Gets the entire content of this file as a String using UTF-8 or specified [charset].
 *
 * This method is not recommended on huge files. It has an internal limitation of 2 GB file size.
 *
 * @param charset character set to use.
 * @return the entire content of this file as a String.
 */
public fun File.readText(charset: Charset = Charsets.UTF_8): String = readBytes().toString(charset)

有時候不曉得是怎麼回事、 看一下源碼就立刻明白了、 是不是很清楚。

大家要注意、Kotlin的擴展函數、是靜態的給擴展類添加擴展函數、 也就是說這樣的擴展、不具備繼承性.

那麼Java中如何使用呢?
來個栗子,

public static void main(String args[]){
    
    File file =  new File("xxx.txt")
    String content = FileKt.readText(file,Charsets.UTF_8)
    sysout(content)
    
}

我們繼續,

4.lambda 閉包


     Runnable thread = new Runnable(){

            public void run(){

                Utils.sayMsg("hello test");
            }
        };

      new Thread(thread).start();
        

java 8 的lambda語法

     new Thread(
                () -> {Utils.sayMsg("hello test");}
        ).start();
   

我們看一下kotlin 的lambda語法

    val  thread = Thread(Runnable {  println("hello world , kotlin !")})

    thread.start()

我們再來看幾種比較靈活坑逼的語法結構:

默認格式:

fun main(args:Array<String>){
    
    val thread = Thread({-> Unit})
    thread.start()
}

如果lambda是沒有參數的,可以省略箭頭符號:
run 函數沒有參數 , 如下

fun main(args:Array<String>){
    val thread = Thread({})
    thread.start()
}

如果lambda是函數的最後一個參數, 可以將大括號放在小括號的外面, 如下:

fun main(args:Array<String>){
    val thread = Thread(){}
    thread.start()
}

如果函數只有一個參數並且這個參數是lambda,則可以省略小括號。如下:

fun main(args:Array<String>){
    val thread = Thread{ } 
    thread.start()
}

lambda閉包聲明:


val echo = {
    name:String, age : Int ->
    
    println("$name , $age")
}


fun main(args:Array<String>){
    
/// 兩種調用方式, 均可以 
//    echo("samuel", 28)
//     echo.invoke("jack",27)

}

5. lambda 的參數上限

如果我們不做特殊處理、我們聲明的lambda閉包會直接默認編譯成匿名內部對象:

Function1<String,Unit> echo = (Function1) echo.INSTANCE;

當然參數個數的上限是22個,如果超出,就會拋異常。
找到kotlin/jvm/functions/ package目錄下的Functions類, 找到最多參數的Function22

/** A function that takes 22 arguments. */
public interface Function22<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, in P20, in P21, in P22, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21, p22: P22): R
}

如果想要傳入更多的參數的話,需要我們手動的添加接口。 當然聲明的類不能聲明和系統包名相同的目錄下。由於java和kotlin是可以相互引用的,用java聲明也是可以的。

6.高階函數

高階的意思就是函數的參數是函數的函數 , 是不是很繞。 來個例子:

fun main(args:Arrays<String>){
    
    ///這是第一種寫法、
    onlyif(true,{

        println("hello , world! only if ")
    })
    //// 第二種寫法、 當lambda 閉包 作爲函數的最後一個參數傳入的時候、是允許寫在小括號之外的。  
    onlyif(true){
        println("hello ,kotlin , only if ")
    }
    
}

////第一個參數是boolean 決定block 函數是否被調用,
/// 第二個參數是一個方法塊, 返回值是Unit , 這裏如果函數作爲方法的參數聲明的話 是不能省略的, 不能像main函數一樣省略Unit關鍵字
fun onlyif(debug:Boolean ,block:()->Unit){
    if(debug) block()
}

函數作爲函數的方法傳遞給函數時的格式爲

 function::run

如果直接用. 分割傳遞、 則傳遞的函數的執行結果。

    val  runable = Runnable{
        println("balabala ")
    }
    
    /// 參數爲空  , 返回值爲Unit 的函數對象
    val fuction : () -> Unit

    fuction = runable::run

    onlyif(true,fuction)
    

lambda 表達是會被編譯成匿名內部類, 如果類中存在大量重複的lambda 表達式則會造成大量無用的匿名內部類。

inline關鍵字

這時候使用inline 關鍵字去修飾高階函數 放在fun 之前, 這樣在編譯期,編譯期就會拆解函數調用爲語句調用。 進而減少創建不必要的對象。


inline fun onlyif(debug:Boolean ,block:()->Unit){
    if(debug) block()
}

下邊是使用inline 關鍵字編譯後生成的代碼

public static final void onlyif(boolean debug, Function0<Unit> block){
/// Function0 後邊這個數字 代表的是 後邊的函數有多少個參數
    if(debug){
        block.invoke();
    }
}

我靠, 這不是靜態函數嗎、 明白inline關鍵字的作用了吧 。

過度使用inline 關鍵字 會增加編譯器的負擔、 增大代碼問題排查難度。
通常我們只會把高階函數修飾爲inline。

7. 總結

多上手敲、多嘗試、多思考、 有問題評論區我們共同討論進步。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章