定义高阶函数
高阶函数和 Lamdba 的关系是密不可分的。像接收 Lambda 参数的函数就可以称为具有函数式编程风格的 API,而如果想定义自己的函数式 API,那就得借助高阶函数来实现了。
高阶函数的定义:如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数。(这里的另一个函数指的是函数类型,就像整型等。)
函数类型的基本语法规则:(String, Int) -> Unit
-> 左边部分用来声明该函数接收什么参数,多个参数之间用逗号隔开,如果不接收任何参数,用空括号表示。而 -> 右边部分用于声明该函数的返回值类型,如果没有返回值就使用 Unit,它大致相当于 Java 中的 void。
高阶函数的定义:将函数类型添加到某个函数的参数声明或者返回值声明上,这个函数就是一个高阶函数了。
1 2 3 fun example (func: (String , Int ) -> Unit ) { func("hello" , 123 ) }
高阶函数的用途:高阶函数允许让函数类型的参数来决定函数的执行逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 fun main () { val num1 = 100 val num2 = 80 val result1 = num1AndNum2(num1,num2,::plus) val result2 = num1AndNum2(num1,num2,::minus) println("result1 is $result1 " ) println("result2 is $result2 " ) } fun num1AndNum2 (num1:Int , num2:Int , operation:(Int ,Int ) -> Int ) :Int { val result = operation(num1,num2) return result } fun plus (num1: Int ,num2: Int ) :Int { return num1 + num2 } fun minus (num1: Int ,num2: Int ) :Int { return num1 - num2 }
Kotlin 中还支持其他多种方式来调用高阶函数,比如 Lambda 表达式、匿名函数、成员引用等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 fun main () { val num1 = 100 val num2 = 80 val result1 = num1AndNum2(num1,num2){ n1, n2 -> n1 + n2 } val result2 = num1AndNum2(num1,num2){ n1, n2 -> n1 - n2 } println("result1 is $result1 " ) println("result2 is $result2 " ) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 fun main () { val list = listOf("Apple" ,"Banana" ,"Pear" ) val result = StringBuilder().build { append("Start eating fruits.\n" ) for (fruit in list){ append(fruit).append("\n" ) } append("Ate all fruits." ) } println(result.toString()) } fun StringBuilder.build (block: StringBuilder .() -> Unit ) : StringBuilder{ block() return this }
内联函数的作用
高阶函数的实现原理 :
Kotlin 的代码最终还是要编译成 Java 字节码的,但 Java 中并没有高阶函数的概念。
Kotlin 的编译器会将这些高阶函数的语法转换成 Java 支持的语法结构,而 Lambda 表达式在底层被转换成了匿名类的实现方式。每调用一次 Lambda 表达式,都会创建一个新的匿名类实例,当然也会造成额外的内存和性能开销。
而内联函数的功能,可以将使用 Lambda 表达式带来的运行时开销完全消除。
1 2 3 4 5 6 7 8 inline fun num1AndNum2 (num1:Int , num2:Int , operation:(Int ,Int ) -> Int ) :Int { val result = operation(num1,num2) return result }
内联函数的工作原理 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 fun main () { val num1 = 100 val num2 = 80 val result = num1AndNum2(num1,num2){n1,n2 -> n1 + n2 } println(result) } fun num1AndNum2 (num1:Int , num2:Int , operation:(Int ,Int ) -> Int ) :Int { val result = operation(num1,num2) return result }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 fun main () { val num1 = 100 val num2 = 80 val result = num1AndNum2(num1,num2) println(result) } fun num1AndNum2 (num1:Int , num2:Int ) :Int { val result = num1 + num2 return result } fun main () { val num1 = 100 val num2 = 80 val result = num1 + num2 println(result) }
noinline 与 crossinline
一个高阶函数中如果接收了两个或者更多函数类型的参数,这时给函数加上了 inline 关键字,那么 Kotlin 编译器会自动将所有引用的 Lambda 表达式全部进行内联。
但如果只想内联其中的一个 Lambda 表达式,可以使用 noinline 关键字。
1 2 3 4 inline fun inlineTest (block1:() -> Unit , noinline block2:() -> Unit ) {}
noinline 关键字的意义在于:内联的函数类型参数在编译的时候会被进行代码替换,因此它没有真正的参数属性。非内联的函数类型参数可以自由地传递给其它任何函数,因为它就是一个真实的参数,而内联的函数类型参数只允许传递给另外一个内联函数,这也是它最大的局限性。
内联函数和非内联函数还有一个重要的区别:内联函数所引用的 Lambda 表达式中是可以使用 return 关键字来进行函数返回的,而非内联函数只能进行局部返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 fun main () { println("main start" ) val str = "" printString(str){ s -> println("lambda start" ) if (s.isEmpty()) return @printString println(s) println("lambda end" ) } println("main end" ) } fun printString (str:String ,block:(String ) -> Unit ) { println("printString begin" ) block(str) println("printString end" ) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 fun main () { println("main start" ) val str = "" printString(str){ s -> println("lambda start" ) if (s.isEmpty()) return println(s) println("lambda end" ) } println("main end" ) } inline fun printString (str:String ,block:(String ) -> Unit ) { println("printString begin" ) block(str) println("printString end" ) }
将高阶函数声明成内联函数是一种良好的编程习惯,事实上,绝大多数高阶函数是可以直接声明成内联函数的,但是也有少部分例外的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 inline fun runRunnable (block:() -> Unit ) { val runnable = Runnable{ block() } runnable.run() } inline fun runRunnable (crossinline block:() -> Unit ) { val runnable = Runnable{ block() } runnable.run() }
高阶函数的应用
高阶函数非常适用于简化各种 API 的调用,一些 API 的原有用法在使用高阶函数简化之后,不管是在易用性还是可读性方面,都可能会有很大的提升。
简化 SharedPreferences 的用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 fun SharedPreferences.open (block: SharedPreferences .Editor .() -> Unit ) { val editor = edit() editor.block() editor.apply() }
使用方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 btnSave.setOnClickListener{ getSharedPreferences("data" ,Context.MODE_PRIVATE).edit { putString("name" ,"Tom" ) putInt("age" ,28 ) putBoolean("married" ,false ) } }
简化 ContentValues 的用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 fun cvOf (vararg pairs: Pair <String , Any?>) = ContentValues().apply{ for (pair in pairs){ val key = pair.first val value = pair.second when (value){ is Int -> put(key, value) is Long -> put(key, value) is Short -> put(key, value) is Float -> put(key, value) is Double -> put(key, value) is Boolean -> put(key, value) is String -> put(key, value) is Byte -> put(key, value) is ByteArray -> put(key, value) null -> putNull(key) } } }
使用方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 val values = contentValuesOf("name" to "Game of Thrones" ,"author" to "George Martin" , "pages" to 720 ,"price" to 20.85 ) db.insert("Book" ,null ,values)
备注
参考资料 :
第一行代码(第3版) 官方文档 官方中文翻译站
传送门 :GitHub