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. 總結
多上手敲、多嘗試、多思考、 有問題評論區我們共同討論進步。