KotlinKotlin函数那么多,你会几个?

Posted 宾有为

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KotlinKotlin函数那么多,你会几个?相关的知识,希望对你有一定的参考价值。

目录

标准函数

Kotlin标准库包含几个函数,其唯一目的是在对象上下文中执行代码块。当对提供了lambda表达式的对象调用这样的函数时,它会形成一个临时作用域。在此范围内,您可以访问不带名称的对象。此类函数称为作用域函数。从技术上讲,作用域函数在许多情况下是可以互换使用的。

let

上下文对象可用作参数(it),引用对象时使用it.。返回值是lambda表达式最后一行代码的结果。可用于调用调用链结果上的一个或多个函数。

var user = User()
// 使用此值作为参数调用指定的函数块并返回其结果。
val let = user.let 
    it.name = "宾有为"
    it.func = "let"
    1 // 返回值 1

print("let:$user,return lambda result:$let")

执行结果:

run

上下文对象可用作接收器(this),引用对象使用this.或直接使用对象值。返回值是lambda表达式最后一行代码的结果。run执行与with相同的操作,但作为上下文对象的扩展函数调用let。当lambda同时包含对象初始化和返回数值时,run非常有用。

var user = User()
// 调用指定的函数块,将此值作为其接收器并返回其结果。
val run = user.run 
    name = "宾有为"
    func = "run"
    1 // 返回值 1

print("run:$user\\nreturn lambda result:$run")

执行结果:

with

非扩展函数:上下文对象作为参数传递,但在lambda内部,它作为接收器(this)可用。返回值是lambda表达式最后一行代码的结果。建议使用来调用上下文对象上的函数,而不提供lambda结果。

var user = User()
// 以给定的接收器作为其接收器调用指定的函数块,并返回其结果。
val with = with(user) 
    name = "宾有为"
    func = "with"
    1 // 返回值 1

print("run:$user\\nreturn lambda result:$with")

执行结果:

apply

上下文对象可用作接收器(this),引用对象使用this.或直接使用对象值。返回值是对象本身。对于不返回值且主要对接收器对象的成员进行操作的代码块,请使用applyapply的常见情况是对象配置。

var user = User()
// 使用此值作为其接收器调用指定的函数块,并返回此值。
val apply = user.apply 
    name = "宾有为"
    func = "apply"

print("also:$apply\\nreturn context object:$apply")

执行结果:

also

上下文对象可用作参数(it),引用对象时使用it.。返回值是对象本身。也适用于执行一些将上下文对象作为参数的操作。还可用于需要引用对象而不是其属性和函数的操作,或者不希望从外部范围隐藏此引用时。

var user = User()
// 使用此值作为参数调用指定的函数块并返回此值。
val also = user.also 
    it.name = "宾有为"
    it.func = "also"

print("also:$user\\nreturn context object:$also")

执行结果:

takeIf

takeIf是类似 if 关键字单个对象的过滤函数,使用方式与takeUnless相反。使用对象进行调用时,如果该对象与lambda的条件匹配,takeIf将返回该对象。否则,返回null

var user = User(name = "宾有为", func = null)
val existName = user.takeIf  it.name != null 
val existSex = user.takeIf  it.func != null 
println("existName: $existName, existSex: $existSex")

执行结果:

takeUnless

takeIf是类似 else 关键字的过滤函数,使用方式与takeIf相反。使用对象进行调用时,如果该对象与lambda的条件匹配,takeIf将返回该对象。否则,返回null

var user = User(name = "宾有为", func = null)
val existName = user.takeUnless  it.name != null 
val existSex = user.takeUnless  it.func != null 
println("existName: $existName, existSex: $existSex")

执行结果:

repeat

repeat,是一个从0开始循环至指定长度的函数,与for (index in 0 until times) 执行的结果一致。

// 从0遍历至10
repeat(10)
    print(it)

执行结果:

小结

Kotlin标准库包含几个函数,其唯一目的是在对象上下文中执行代码块。当对提供了lambda表达式的对象调用这样的函数时,它会形成一个临时作用域。在此范围内,您可以访问不带名称的对象。此类函数称为作用域函数。作用域函数有五个:letrunwithapplyalso

作用域函数的区别

作用域函数使用场景

函数使用场景
let1、对非空对象执行lambda
2、在局部范围中引入表达式作为变量
with对对象的函数调用进行分组
run1、对象配置和计算结果
2、在需要表达式的地方运行语句
apply1、对象配置和计算结果
2、不返回值且主要对接收器对象的成员进行操作的代码块
also1、执行一些将上下文对象作为参数的操作。
2、需要引用对象而不是其属性和函数的操作
3、不希望从外部范围隐藏此引用时

简化函数

kotlin中,变量可以通过等号赋值,函数同样被允许使用等号进行赋值,这些使用等号赋值的函数就叫做简化函数。

简化函数的编写规范是有所要求的,如果函数表达式只有一行,才可以使用等号赋予函数其表达式。简化函数默认return函数的最后一行代码。

fun main(args: Array<String>) 
    println(test1())// result:2
    println(test2())// result:简化函数

// 执行表达式 1+1
private fun test1() = 1+1
// 返回表达式"简化函数",简化函数如果有返回类型,表达式的执行结果必须是一个可以返回的类型。
private fun test2() : String = "简化函数"

尾递归函数(tailrec)

kotlin存在一种特殊的递归函数——尾递归函数,指的是函数末尾的返回值重复调用了自身函数。使用时只需要在函数的fun前面加上tailrec关键字,编译器在编译时会自动优化递归,使用循环方式代替递归,从而避免栈溢出的情况,以此提高程序性能。

fun main(args: Array<String>) 
    print(factorial(5)) // result:120


// 求 i 的阶乘( i * i-1 )
tailrec fun factorial(i: Int): Int 
    if (i != 1) 
        return i * factorial(i - 1)
     else 
        return i
    

扩展函数

把接收者类型,放到即将添加的函数前面,通过类可以对其调用的函数称为扩展函数。

如图所示,接收者类型写在了test函数的前边,在扩展函数里,使用this引用的是接收者l类型的对象,而非当前Test类。


如下图所示,通过String类型还无法调用test函数,我们在函数名称的前面加上String类型,再次通过类型就可以引用test函数。


扩展函数只能由接收者类型调用,不能通过扩展函数所在的类调用。

扩展函数不仅可以扩展函数,还可以扩展属性。

val String.lastIndex: Int
    get() = 0

fun main(args: Array<String>) 
    var a = "aaaa000"
    print(a.lastIndex) // result:0

小知识

  • 扩展函数不可以重写。
  • 扩展函数实质上是静态函数。
  • 在扩展函数里使用this引用的是扩展函数的类型,而不是函数当前所在类。
  • 如果扩展函数在其接收者类型之外声明,则它不能访问接收者privateprotected成员。
  • 扩展函数、属性的代码优先级高于接收者类型原有的函数、对象,等同于重写接收者的同名函数、属性。

高阶函数

高阶函数是将函数作为参数或返回函数的函数。

在以下的示例代码中,testFun1(i: Int, face: (String) -> String)就是一个高阶函数,因为它接受一个函数值作为它的第二个参数。

fun main(args: Array<String>) 
    testFun1(1)  it ->
        // 实现业务逻辑,将it给return回去
        it
    


// face: (String) -> String  等于 调用高阶函数使用的名称: (需要传递的参数类型) -> 返回类型
fun testFun1(i: Int, face: (String) -> String) 
	// 接收到face返回值,并将其print
    val value = face("testFun")
    println("testFun:$value")

在实现高阶函数的lambda表达式里(如图main函数对testFun1的调用),若设置有返回值,则默认return最后一行的代码结果,若不设置,则返回Unit

运行结果:

高阶函数除了上述使用方式之外,还可以根据需求传入不同函数对逻辑进行处理。

fun main(args: Array<String>) 
	val plusResult = num1AndNum2(20, 30)  n1: Int, n2: Int ->
        // 执行运算    
        n1 + n2
    
    println("$plusResult") // result:50

    val minusResult = num1AndNum2(20, 30)  n1: Int, n2: Int ->
        // 执行运算
        n1 - n2
    
    println("$minusResult") // result:-10


fun num1AndNum2(num1: Int, num2: Int, block: (Int, Int) -> Int): Int 
    return block(num1, num2)

除此之外,高阶函数的lambda表达式还可以拆分成lambda表达式变量、匿名函数,分别以变量、匿名函数的形式传入高阶函数。

fun main(args: Array<String>) 
    val minusResult = num1AndNum2(20, 30, lambda1)
    println("$minusResult") // result:-10

    val aaa = num1AndNum2(20, 30, lambda2) // result:50
    println("$aaa")

	val anonymous1 = num1AndNum2(20, 30, a) // result:-10
    val anonymous2 = num1AndNum2(20, 30, b) // result:50
	// 匿名函数
	num1AndNum2(20,30,fun(x,y) = x + y) // result:50

// lambda表达式
val lambda1 =  x: Int, y: Int -> x - y 
// lambda表达式
val lambda2: (Int, Int) -> Int =  x: Int, y: Int -> x + y 
// 匿名函数
val a = fun(x : Int, y : Int): Int = x - y
// 匿名函数
val b = (fun(x : Int, y : Int): Int = x + y)

fun num1AndNum2(num1: Int, num2: Int, block: (Int, Int) -> Int): Int 
    return block(num1, num2)

内联函数(inline)

inline

lambda表达式在底层被转换成了匿名内部类的实现方式,每调用一次 lambda 表达式,都会创建一个新的匿名类实例,会造成额外的内存和性能开销,但这种开销可以通过inline lambda表达式来消除。使用inline关键字修饰的函数也被称为内联函数。

使用inline关键字,需结合反编译才能看见效果。

fun inlineFun(action: (() -> Unit))
    println("inlineFun: 调用前...")
    action()
    println("inlineFun: 调用后...")


fun main(args: Array<String>) 
    inlineFun 
        println("inlineFun: 正在调用...")
    

使用Idea、android Studio开发工具反编译代码步骤:在开发工具的顶部菜单栏 Tools > Kotlin > Show Kotlin Bytecodes > Decompile。

inlineFun未添加inline函数代码反编译结果:


inlineFun添加inline函数代码反编译结果:


通过两次添加inline关键字前后的反编译比对,可以看出inline函数是将表达式转移到调用方,通过这样的方式减少lambda创建新匿名类造成的开销。

当你尝试在没有lambda表达式的函数上使用inline时,编译器则会提示inline对性能预期影响微不足道,应该结合高阶函数一起使用的警告。

Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types.

需要注意的是,inline函数不支持修饰变量(包含持有高阶函数的变量),只能用于修饰高阶函数。

noinline

如果不希望传递给inline函数的所有lambda都被内联,请使用修饰符noinline标记一些不需要inline的函数参数(仅限lambda表达式)。使用noinline的前提是使用的函数必须是inline函数。

inline fun inlineFun(noinline action: (() -> Unit))
    println("inlineFun: 调用前...")
    action()
    println("inlineFun: 调用后...")


fun main(args: Array<String>) 
    inlineFun
        println("inlineFun: 调用中...")
    

添加noinline代码反编译后,“调用中…”的字符并没有被反编译出来,而是用action$iv.invoke();调用函数。

crossinline

crosinline用于禁止传递给内联函数的lambda中的非局部返回。

在内联函数的lambda表达式里,使用return会中断高阶函数后面代码的执行,讲inline函数有讲解到:inline函数是将表达式转移到调用方,因此下面代码的“调用后”是不会print

inline fun inlineFun(action: (() -> Unit)) 
    println("inlineFun: 调用前...")
    action()
    println("inlineFun: 调用后...")


fun main(args: Array<String>) 
    inlineFun 
        println("inlineFun: 调用中...")
        return
    

使用crossinline关键字,则可以杜绝这种情况。inline函数添加crossinline后,return将会报'return' is not allowed here的错误。

部分博客把crossinline的含义解释成“检查代码中是否有return,如果有则会执行不通过”。这样的说法是片面的,官方文档已经解释了,是禁止非局部返回。如果高阶函数里面的return不影响到嗲用高阶函数的函数后面代码执行,是可以使用return,如下:


匿名函数

省略了函数名字的函数称之为匿名函数。

匿名函数往往结合高阶函数一起使用,匿名函数默认隐式return最后一行代码,写法有以下三种:

val a = fun(str: String): String = "a"
val b = (fun(str: String): String = "b")

参数和返回类型的指定方式与高阶函数相同,如果可以从上下文中推断出参数类型,则可以省略参数类型。如下:

// 匿名函数
println(fun(item) = "c")
// 高阶函数
fun println(str : (String) -> String)
    println(str("ttt"))

匿名函数与lambda表达式类似,写法不同,但执行效果可以是一致的,如下lambda函数等同于上面的匿名函数写法:

var d: (it: String) -> String =  "d" 

var e: (String) -> String =  "e" 

var f =  it: String -> "f" 

看了好几篇的博客,大部分都是说lambda表达式就是匿名函数,我在kotlin官方文档找到的匿名函数写法并不包含lambda表达式,同时也不知道其它作者所表达的lambda表达式就是匿名函数的依据来自于哪里。

在官方文档的匿名函数介绍里,官方讲解了匿名函数与lambda表达式的区别:lambda表达式和匿名函数之间的另一个区别是非本地返回的行为。没有标签的return语句总是从用fun关键字声明的函数返回。这意味着lambda表达式内的返回将从封闭函数返回,而匿名函数内的返回则从匿名函数本身返回。

小结

  • 将匿名函数作为参数传递时,将它们放在括号内。允许您将函数放在括号外的速记语法仅适用于 lambda表达式。

参考文档
1、高阶函数
2、标准、作用域函数
3、Kotlin之高阶函数
4、《Kotlin从零到精通》—— 函数的运用
5、Android筑基,Kotlin扩展函数详解——郭霖
6、Kotlin Doc——Extensions
7、Kotlin Keywords and operators

以上是关于KotlinKotlin函数那么多,你会几个?的主要内容,如果未能解决你的问题,请参考以下文章

网易音乐前端实习程序员面试的10个问题,你会几个?

单例模式的十种写法,你会几个?(重写版)

有点难度的消息队列3个面试题,你会几个?

假如高考考python编程,这些题目你会几个呢?

[原创]Shell脚本大量示例,你会几个?

Spring 中的 18 个注解,你会几个?