高阶函数是指在 Kotlin 中, 使用函数作为变量或者返回值 的函数
fun todo(block: () -> Unit) {
block()
}
var block: () -> Unit // 代表一个没有传入参数,无返回值的函数
var block: (T) -> R // 代表一个传入参数类型为 T ,返回值类型为 R 的函数
使用 lambda 表达式,我们可以更简洁地使用 Kotlin 的高阶函数
fun function01(i: Int, block: (Int) -> Unit) {
// do something
}
fun function02(block: (Int) -> Unit) {
// do something
}
fun main() {
// 直接使用匿名函数
function01(666, { num -> println(num) })
// 当函数的最后一个参数是函数时,我们可以使用 Lambda 表达式进行简化
function01(666) { num -> println(num) }
// 如果函数只传入一个参数,且这个参数是函数,则可以直接省略小括号
function02 { num -> println(num) }
// 如果传入的函数的参数只有一个,则这个参数可以省略
// 并且在代码块中可以用 it 来表示它
function01(666) { println(it) }
function02 { println(it) }
}
fun function(block: (Int) -> Unit) {
block()
}
fun printInt(num: Int) {
println(num)
}
// 下面的代码展示了如何将 printInt 函数
// 转化为一个 (Int) -> Unit 类型的函数参数
fun main() {
function(::printInt)
}
在 Kotlin 中,可以给函数加上 inline 前缀,表示该函数是内联函数
如果不进行内联,则在传入函数类型参数时,实际上是新创建了一个对象然后传入
如果进行内联,则不会新创建一个对象,而是将传入函数的代码直接插入到调用该内联函数处
一般只有在函数参数列表中包含有高阶函数时才使用 inline 关键字,因为 Java 会识别函数并在特定情况下进行自动内联,因此通过加入 inline 关键字来优化调用这些函数所消耗的性能是微不足道的
// 不使用 inline
fun function(block: () -> Unit) {
block()
}
// 使用 inline
inline fun function(block: () -> Unit) {
block()
}
// 调用
fun main() {
function { println() }
}
// 从 Java 的角度看,大概是下面这样
// 不使用 inline
public static void main(String[] args) {
function(new Function() {
@Override
public void invoke(int number) {
System.out.println();
}
});
}
// 使用 inline
public static final void function() {
System.out.println();
}
public static void main(String[] args) {
function();
}
使用内联函数没有创建新对象,节约了性能的开销
在一个 内联函数 中,如果我们不希望其中的函数类型参数被内联,则可以在 该函数类型参数前 加上前缀 noinline ,使其不被内联
inline fun function(block01: () -> Unit, noinline block02: () -> Unit) {
block01()
block02()
}
inline 可以节约性能开销,这很不错,那么 为什么 需要 noinline 关键字呢?
因为加入了 inline 关键字,将意味着传入的函数将 失去参数属性 ,也就意味着这个函数已经 不能继续当作参数 来进行传递,而 只能在内联函数之间进行传递 ,如果我们需要传入的函数继续以函数类型参数来进行传递,我们就需要使用非内联函数
从 Java 的角度来看,当一个函数类型参数被内联以后,这个函数将不再是对象,它已经变成了一个方法,既然不是对象而是方法,在 Java 中自然无法把这个函数当作参数来传递了
当函数被内联以后,这个函数将变成一段真正的代码块,这时我们 直接使用 return ,将会…
inline fun function(block: () -> Unit) {
block()
block()
}
fun main() {
function {
println("666")
return
}
}
// 运行结果
// 666
我们发现,程序并没有按我们预期的结果运行,只打印出了 一个 666
如果需要我们的结果,我们可以这样改
// 没有使用 crossinline 关键字
inline fun function(block: () -> Unit) {
block()
block()
}
fun main() {
function {
println("666")
// 这里改啦
return@function
}
}
// 运行结果
// 666
// 666
或者这样改:
// 使用 crossinline 关键字
// 在函数类型参数前加入 crossinline 关键字
inline fun function(crossinline block: () -> Unit) {
block()
block()
}
fun main() {
function {
println("666")
return@function
}
}
// 运行结果
// 666
// 666
发现除了加了 crossinline 关键字以外,并没有什么不同
但实际上如果我们使用 crossinline 关键字,并把 main 函数改成下面这样
fun main() {
function {
println("666")
// 直接 return
return
}
}
上面的代码的 return 那一行就会报错,无法编译,提示的错误是
'return' is not allowed here
容易发现,加入 crossinline 关键字,就可以 禁止传入的函数类型参数的代码中使用全局返回
在 Kotlin 中提供了很多标准内联拓展函数,下面简单介绍一下比较常用的 let、run、apply、also 以及用法类似的但并不是内联拓展函数的 with 函数
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
// Kotlin
data class MusicData(
private val music: String,
private val artist: String
)
fun showMusicData(data: MusicData) {
val num = data.let {
println("music is ${it.music}")
println("artist is ${it.artist}")
musicTextView.text = it.music
artistTextView.text = it.artist
1000
}
println(num)
}
// Java
class MusicData {
private final String music;
private final String artist;
public MusicData(String music, String artist) {
this.music = music;
this.artist = artist;
}
public String getMusic() {
return music;
}
public String getArtist() {
return artist;
}
}
public void showMusicData(MusicData data) {
System.out.println("music is" + data.music);
System.out.println("artist is" + data.artist);
musicTextView.setText(data.music);
artistTextView.setText(data.artist);
int num = 1000;
System.out.println(num);
}
// formal
fun initView(btn: Button) {
btn.text = "text"
btn.setOnClickListener {
// do something
}
}
// use Kotlin let
fun initView(btn: Button) {
btn.let {
it.text = "text"
it.setOnClickListener {
// do something
}
}
}
fun todo() {
any?.let {
// 若any 不为空,则执行 let 内的代码块
// 并将代码块的返回值作为 let 函数的返回值返回
it.todo()
// do something
}
}
准确来说 with 只是一个内联函数,而不是一个内联拓展函数,但其用法跟 Kotlin 提供的标准内联拓展函数的使用方式类似,因此在这里也记录一下
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
// Kotlin
fun showMusicData(data: MusicData) { val num = with(data) {
println("music is $music")
println("artist is $artist")
musicTextView.text = music
artistTextView.text = artist
99
}
println(num)
}
// Java
public void showMusicData(MusicData data) {
System.out.println("music is" + data.music);
System.out.println("artist is" + data.artist);
musicTextView.setText(data.music);
artistTextView.setText(data.artist);
int num = 99;
System.out.println(num);
}
// 例如在 RecyclerView 的 Adapter
// 的 onBindViewHolder 方法中
private val list: List<MusicData> = ...
// formal
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.textView.text = list[position].music
// holder.imageView.set...
}
// use Kotlin with
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
with(holder){
textView.text = list[position].music
// imageView.set...
}
}
public inline fun <T, R> T.run(block: T.() -> R): R { contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
其实就是 let 和 with 的结合体
// Kotlin
fun showMusicData(data: MusicData) {
val num = data.run {
println("music is $music")
println("artist is $artist")
musicTextView.text = music
artistTextView.text = artist
1000
}
println(num)
}
// Java
public void showMusicData(MusicData data) {
System.out.println("music is" + data.music);
System.out.println("artist is" + data.artist);
musicTextView.setTex(data.music);
artistTextView.setText(data.artist);
int num = 1000;
System.out.println(num);
}
// formal
fun showData(list: List<MusicData?>, index: Int){
val data = list[index]?: return
println("music data is $data")
}
// use Kotlin let
fun showData(list: List<MusicData?>, index: Int) {
list[index]?.let {
println("music data is $it")
}
}
// use Kotlin with
fun showData(list: List<MusicData?>, index: Int) {
val data = list[index]?: return with(data) {
println("music data is $this")
}
}
// use Kotlin run
fun showData(list: List<MusicData?>, index: Int) {
list[index]?.run {
println("music data is $this")
}
}
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
跟 run 很相似,唯一的不同就是 run 返回传入函数的返回值 ,而 apply 返回调用的对象本身
// Kotlin
fun showMusicData(data: MusicData) {
val musicData = data.apply {
println("music is $music")
println("artist is $artist")
musicTextView.text = music
artistTextView.text = artist
}
}
// Java
public void showMusicData(MusicData data) {
System.out.println("music is" + data.music);
System.out.println("artist is" + data.artist);
musicTextView.setText(data.music);
artistTextView.setText(data.artist);
MusicData musicData = data;
}
// formal
fun initTextView(): TextView {
val textView = TextView()
textView.text = "text"
textView.textSize = 24F
textView.textColor = Color.Black
textView.setOnClickListener {
// do something
}
return textView
}
// use Kotlin apply
fun initTextView(): TextView {
return TextView().apply {
text = "text"
textSize = 24F
textColor = Color.Black
setOnClickListener {
// do something
}
}
}
// formal
class MyView{
fun function_1(): MyView {
// do something
return this
}
fun function_2(): MyView {
// do something
return this
}
fun function_3(): MyView {
// do something
return this
}
}
// use Kotlin apply
class MyView{
fun function_1() = apply {
// do something
}
fun function_2() = apply {
// do something
}
fun function_3() = apply {
// do something
}
}
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
also 和 let 、apply 非常相似
跟 let 的区别在于 let 的返回值类型是传入函数的返回值,而 also 返回的是调用者本身
跟 apply 的区别在于使用 apply 时,我们在代码块内使用 this 来代替调用者本身,而 also 则是使用 it 来代替调用者本身
// Kotlin
fun showMusicData(data: MusicData) {
val musicData = data.also {
println("music is ${it.music}")
println("artist is ${it.artist}")
musicTextView.text = it.music
artistTextView.text = it.artist
}
}
// Java
public void showMusicData(MusicData data) {
System.out.println("music is" + data.music);
System.out.println("artist is" + data.artist);
musicTextView.setText = data.music;
artistTextView.setText = data.artist;
MusicData musicData = data;
}
高阶函数在 Android Kotlin 项目的实际开发中的应用还是比较多的,在某些场景下使用高阶函数,相比 Java 会大大减少代码量,代码也看起来更加优雅
内联拓展函数 则是对高阶函数使用的一些进阶方法,上面举例的用法不过是笔者的一些总结罢了,其实实际项目中有不少地方都可以使用内联拓展函数来简化代码
高阶函数是 Kotlin 语言相比 Java 语言的一个非常大的不同,它提供了更灵活的代码处理方式,同时它也意味着 在 Kotlin 中,函数不是存在于类之下,它与类是站在同一高度上的