Kotlin 学习 高阶函数

摘自第一行Android代码第3版

定义

如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个和函数,那么该函数就称为高阶函数

// 语法示例
(String,Int) -> Unit

-> 左边的部分就是用来声明函数接收什么参数,多个参数之间使用逗号隔开,如果不接受任何参数,写一对空括号就可以了。而->右边的部分用于声明该函数的返回值是什么类型,如果没有任何返回值就使用Unit,它大致相当于Java中的void

举个例子

fun example(func : (String , Int) -> Unit) {
	func("hello" , 123)
}		

来点实战

package com.example.broadcastbestpractice

/**
 * Create with IntelliJ IDEA.
 * Description:
 * User: coco
 * Date: 2022-01-07
 * Time: 10:56
 */


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
}

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")
}

使用lambda 替换

	val result1 = num1AndNum2(num1 , num2) { n1 , n2 -> n1 + n2 }
	val result2 = num1AndNum2(num1 , num2) { n1 , n2 -> n1 - n2 }

另一个例子

// 在函数类的前面加上ClassName就表示这个函数类型是定义在那个类当中
fun StringBuilder.build(block : StringBuilder.() -> Unit) : StringBuilder {
	block()
	return this
}

fun main() {
	val list = listOf("Apple" , "Banana" , "Orange" , "Pear" , "Grape")
	val result = StringBuilder().build {
		append("Start eating fruits.\n")
		for (fruit in list) {
			append(fruit).append("\n")
		}
		append("Ate all fruits.")
	}
	println(result.toString())
}

内联函数的使用

内联函数的用法非常简单,只需要在定义高阶函数时加上inline关键字的声明即可

inline fun num1AndNum2(num1 : Int , num2 : Int , operation : (Int , Int) -> Int) : Int {
	val result = operation(num1 , num2)
	return result
}

noline与crossinline

一个高阶函数中如果接收了两个或者更多个函数类型的参数,这时我们给的函数加上inline关键字,那么kotlin编译器会自动将所有引用的Lambda表达式全部进行内联

但是,如果我们只想内联其中的一个Lambda表达式该怎么办呢?这时就可以使用noinline关键字

inline fun inlineTest(block1 : () -> Unit , noinline block2 : () -> Unit) {
	
}

可以看到,这里使用inline关键字声明了inlineTest()函数,原来block1和block2这两个函数类参数所引用的Lambda表达式都会被内联。但是我们在block2参数的前面又加上了一个noinline关键字,那么现在就只会对block1参数所引起的Lambda表达式进行内联了,这就是noinline关键字的作用

为什么要用noinine取消内联功能

内联的函数类型参数在编译的时候会被进行代码替换,因此它没有真正的参数属性。非内联的函数参数类型可以自由地传递给其他任何函数,因为它就是一个真实的参数,而内联的函数类型参数只允许传递给另一个内联函数,这也是它最大的局限性

另外还有一个重要的区别,那就是内联函数所引用的Lambda表达式中可以使用return关键字来进行函数返回的,而非内联函数只能进行局部返回

package com.example.broadcastbestpractice

/**
 * Create with IntelliJ IDEA.
 * Description:
 * User: coco
 * Date: 2022-01-07
 * Time: 17:32
 */

fun printString(str : String , block : (String) -> Unit) {
	println("printString begin")
	block(str)
	println("printString end")
}

fun main() {
	println("main start")
	val str = ""
	printString(str) { s ->
		println("lambda start")
		if (s.isEmpty()) return@printString
		println(s)
		println("lambda end")
	}
}

输出如下

main start
printString begin
lambda start
printString end

Lambda表达式是不允许直接使用return关键字的,这里使用了return@printString的写法,表示进行局部返回,并且不在执行Lambda的剩余表达式

内联函数时的打印输出情况

inline fun printString(str : String , block : (String) -> Unit) {
	println("printString start")
	block(str)
	println("pinttString end")
}

fun main() {
	println("main start")
	val str = ""
	printString(str) { s ->
		println("lambda start")
		if (s.isEmpty()) return
		println(s)
		println("lambda end")
	}
}

输出如下

main start
printString start
lambda start

现在printString()函数已经变成了内联函数,我们就可以在Lambda表达式中使用return关键字。此时的return代表的是返回外层的调用函数,也就是main()函数

crossinline

将高阶函数直接声明成内联函数是一种良好的编程习惯,事实上,绝大多数高阶函数是可以直接声明成内联函数的,但是也有少部分例外的情况

inline fun runRunnable(block : () -> Unit){
	val runnable = Runnable{
		block()
	}
	runnable.run()
}

这段代码在没有加上inline关键字声明的时候绝对是可以工作的,但是在加上inline关键字之后就会提示错误
Can’t inline ‘block’ here: it may contain non-local returns. Add ‘crossinline’ modifier to parameter declaration ‘block’
借助crossinline 的声明,代码就可以郑航编译通过了


版权声明:本文为weixin_42817311原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。