Kotlin高阶函数、内联函数、内联拓展函数

Kotlin高阶函数

高阶函数

高阶函数是什么?

高阶函数是指在 Kotlin 中, 使用函数作为变量或者返回值 的函数

fun todo(block: () -> Unit) {
    block()
}  

高阶函数的类型

var block: () -> Unit // 代表一个没有传入参数,无返回值的函数
var block: (T) -> R // 代表一个传入参数类型为 T ,返回值类型为 R 的函数

Lambda 表达式

使用 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)
}

内联函数

inline

在 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

在一个 内联函数 中,如果我们不希望其中的函数类型参数被内联,则可以在 该函数类型参数前 加上前缀 noinline ,使其不被内联

inline fun function(block01: () -> Unit, noinline block02: () -> Unit) {
    block01()
    block02()
}

inline 可以节约性能开销,这很不错,那么 为什么 需要 noinline 关键字呢?

因为加入了 inline 关键字,将意味着传入的函数将 失去参数属性 ,也就意味着这个函数已经 不能继续当作参数 来进行传递,而 只能在内联函数之间进行传递 ,如果我们需要传入的函数继续以函数类型参数来进行传递,我们就需要使用非内联函数

从 Java 的角度来看,当一个函数类型参数被内联以后,这个函数将不再是对象,它已经变成了一个方法,既然不是对象而是方法,在 Java 中自然无法把这个函数当作参数来传递了

crossinline

当函数被内联以后,这个函数将变成一段真正的代码块,这时我们 直接使用 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 函数

let

public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}
Kotlin let 函数的 Java 翻译
// 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);
}
使用场景
  • 指定一变量在特定作用域内使用 it 替代
// 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

准确来说 with 只是一个内联函数,而不是一个内联拓展函数,但其用法跟 Kotlin 提供的标准内联拓展函数的使用方式类似,因此在这里也记录一下

public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}
Kotlin with 函数的 Java 翻译
// 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...
   }
}

run

public inline fun <T, R> T.run(block: T.() -> R): R {    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

其实就是 let 和 with 的结合体

Kotlin run 函数的 Java 翻译
// 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);
}
使用场景
  • let 和 with 的结合体
// 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")
    }
}

apply

public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

跟 run 很相似,唯一的不同就是 run 返回传入函数的返回值 ,而 apply 返回调用的对象本身

Kotlin apply 函数的 Java 翻译
// 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
    }
}

also

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 also 的 Java 翻译
// 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;
}
使用场景
  • 与 apply 函数类似

总结

高阶函数在 Android Kotlin 项目的实际开发中的应用还是比较多的,在某些场景下使用高阶函数,相比 Java 会大大减少代码量,代码也看起来更加优雅

内联拓展函数 则是对高阶函数使用的一些进阶方法,上面举例的用法不过是笔者的一些总结罢了,其实实际项目中有不少地方都可以使用内联拓展函数来简化代码

高阶函数是 Kotlin 语言相比 Java 语言的一个非常大的不同,它提供了更灵活的代码处理方式,同时它也意味着 在 Kotlin 中,函数不是存在于类之下,它与类是站在同一高度上的

你可能感兴趣的:(笔记,kotlin,android)