kotlin函数

Posted gengqiquan

tags:

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

尊重他人的劳动成果,转载请标明出处:https://blog.csdn.net/gengqiquan/article/details//87982480, 本文出自:【gengqiquan的博客】

infix标记法

  • 在以下情况下,也可以使用中缀符号调用函数:
  • 它们是成员函数或扩展函数-它们只有一个参数
  • 它们用infix关键字标记
// Define extension to Int
infix fun Int.shl(x: Int): Int  ...

// call extension function using infix notation
1 shl 2
// is the same as
1.shl(2)

可变参数

我们可以通过vararg修饰符来表明一个参数是可变参数来允许向函数传递可变数量的参数

fun <T> asList(vararg ts: T): List<T>  val result = ArrayList<T>()
for (t in ts) // ts is an Array
result.add(t) return result


val list = asList(1, 2, 3)

泛型函数

fun <T> singletonList(item: T): List<T>  // ...

尾部递归函数

Kotlin支持一种称为尾部l递归的函数式编程。这使得一些通常使用循环编写的算法可以使用递归函数编写,但不存在堆栈溢出的风险。当一个函数被标记为tailrec修饰符并满足所需的形式时,编译器会优化递归,而留下一个快速有效的基于循环的版本。

tailrec fun findFixPoint(x: Double = 1.0): Double
= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))

要符合tailrec修饰符的条件,函数必须调用自身作为它执行的最后一个操作。当递归调用后有更多的代码时,不能使用尾部递归,并且不能在try/catch/finally块中使用尾部递归。目前,只有JVM后端支持尾部递归。

高阶函数和lambda

高阶函数

高阶函数是以函数为参数(在高阶函数内部调用执行:类似代理)或返回函数的函数。

fun <T> lock(lock: Lock, body: () -> T): T  lock.lock()
try 
return body()

finally 
lock.unlock() 

body有一个函数类型:()->t,所以它应该是一个不带参数并返回t类型值的函数。它在try块内被调用,同时受锁保护,其结果由lock()函数返回。

要调用lock(),可以将另一个函数作为参数传递给它(请参见函数引用):

fun toBeSynchronized() = sharedResource.operation() val result = lock(lock, ::toBeSynchronized)

或者使用lambda

val result = lock(lock,  sharedResource.operation() )

lambda

  • lambda表达式总是由大括号包围,
  • 其参数(如果有)在->之前声明(参数类型可以省略),-正文在->之后(如果存在)。
  • 在Kotlin中,有一种约定,即如果函数的最后一个参数是函数,并且要将lambda表达式作为相应的参数传递,则可以把它写在括号后面

如果一个函数文本只有一个参数,则可以省略它的参数声明(连同->),然后用it来指代这个参数

strings.filter  it.length == 5 .sortBy  it .map  it.toUpperCase() 

1.1版本以后,未使用到的参数可以用下划线:“_”替代

map.forEach  _, value -> println("$value!") 

lambda表达式或匿名函数是一种“显式声明函数”,即未声明但可以作为表达式立即传递

max(strings,  a, b -> a.length < b.length )

等价于

fun compare(a: String, b: String): Boolean = a.length < b.length

max(strings, compare(a, b))

我们可以使用限定返回语法(return@方法名)从lambda显式返回值。否则,将隐式返回最后一个表达式的值。因此,以下两个片段是等效的:

ints.filter 
val shouldFilter = it > 0 shouldFilter

ints.filter 
val shouldFilter = it > 0 return@filter shouldFilter

匿名函数

如果可以通过上下文自动推断出函数返回值的类型,可以用匿名函数来替代

fun(x: Int, y: Int): Int  return x + y

匿名函数可以是一个表达式或者代码块
参数和返回类型的指定方式与常规函数相同,但如果可以从上下文推断参数类型,则可以忽略这些参数类型

注意,匿名函数参数总是在括号内传递。允许将函数保留在括号之外的简短语法仅适用于lambda表达式。
lambda表达式和匿名函数之间的另一个区别是非局部返回的行为。一个无标签返回语句总是从用fun关键字声明的函数返回。这意味着lambda表达式内部的返回将从封闭函数返回,而匿名函数内部的返回将从匿名函数本身返回。

闭包

lambda表达式或匿名函数(以及局部函数和对象表达式)可以访问其闭包,即在外部作用域中声明的变量。与Java不同,在封闭中使用到的外部变量可以被修改,如下函数可以通过编译并被正常执行:

var sum = 0
ints.filter  it > 0 .forEach 
sum += it 
print(sum)

带接收器的函数表达式

Kotlin提供了使用指定的接收器对象调用函数文本的能力。在函数文本的主体中,您可以对该接收器对象调用方法,而不需要任何附加限定符。这类似于扩展函数,它允许您访问函数体内部的接收器对象的成员

sum : Int.(other: Int) -> Int

1.sum(2)

匿名函数语法允许您直接指定函数文本的接收器类型。你可以用接收器声明一个函数类型的变量,稍后再使用它。

val sum = fun Int.(other: Int): Int = this + other

当可以从上下文推断接收器类型时,lambda表达式可以用作带有接收器函数表达式。

class html 
fun body()  ... 


fun html(init: HTML.() -> Unit): HTML 
val html = HTML() // create the receiver object
html.init() // pass the receiver object to the lambda
return html


html  // lambda with receiver begins here
body() // calling a method on the receiver object

JetBrains团队成员yole对接收器的函数表达式和扩展函数的区别给的回复是:带接收器的lambda可以传递给另一个函数,以便另一个函数调用它,并传递您可以在lambda主体中访问的接收器对象。扩展函数是一种只能直接调用的函数。

内联函数

使用高阶函数会带来一些运行时损害:每个函数都是一个对象,当它俘获一个闭包,即在函数体中访问的那些变量,会带来运行时内存分配(包括函数对象和类)和虚拟调用的额外开销。
但在许多情况下,通过内联lambda表达式可以消除这种开销。比如:

lock(l)  foo() 

编译器为了创建一个函数对象并调用,可能会生成以下代码

l.lock() try 
foo()
finally 
l.unlock()

为了让编译器做到这一点,我们可以在函数声明前加上inline修饰符

inline fun lock<T>(lock: Lock, body: () -> T): T  // ...

内联修饰符同时影响函数本身和传递给它的lambda:所有这些修饰符都将内联到调用代码处(类似Java早期的final方法)。内联可能会导致生成的代码增长,但如果我们以合理的方式进行(不要内联大函数),它将在性能上,尤其是在循环内的调用带来很大的提升。

如果你只希望传递给内联函数的某些lambda参数是内联的,则可以使用noinline修饰符标记其他的你不希望进行内联的函数参数

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit)  // ...

可内联lambda只能在inline函数内部调用或作为可内联参数传递,但是noinline参数可以以我们喜欢的任何方式进行操作:存储在字段中、传递等。
注意,如果一个内联函数没有不可内联的函数参数,也没有具体化的类型参数,编译器将发出警告,因为这样的函数不太可能是有益的(如果您确定需要内联,可以取消警告)。

在Kotlin中,我们只能使用普通的非限定返回来退出命名函数或匿名函数。这意味着要退出lambda,我们必须使用一个标签,并且lambda内禁止裸返回,因为lambda不能作为封闭函数返回

fun foo() 
ordinaryFunction 
return // ERROR: can not make `foo` return here 

但是,如果lambda传递给的函数是内联的,则返回也可以是内联的,因此如下代码是允许的:

fun foo()  
inlineFunction 
return // OK: the lambda is inlined 

注意,一些内联函数可能将传递给它们的lambda作为参数,而不是直接从函数体调用。比如将他从另一个执行上下文(本地对象或嵌套函数)调用,在这种情况下,非本地控制流也不允许出现在lambda中。要做到这一点,lambda参数需要用crossinline修饰符进行标记:

inline fun f(crossinline body: () -> Unit)  val f = object: Runnable 
override fun run() = body() 
// ...

break和continue在内联lambda中尚不可用,但是开发团队也计划支持它们。

具体化类型参数

有时我们需要将一个类型作为参数传递给函数,但是我们又不希望编写太复杂的获取代码,我们希望能这样简洁的声明一个需要类型的函数

treeNode.findParentOfType<MyTreeNode>()

我们提供reified修饰符来实现,比如上面的代码的声明如下:

inline fun <reified T> TreeNode.findParentOfType(): T?  
var p = parent
while (p != null && p !is T) 
p = p.parent

return p as T? 

我们用具体化的修饰符限定了类型参数,现在它可以在函数内部访问,就好像它是一个普通类一样。因为函数是内联的,所以不需要反射,普通的操作符就像!现在正在工作。此外,我们可以如上所述调用它:mytree.findparentoftype()。

虽然在许多情况下可能不需要反射,但我们仍然可以将其与一个已具体化的类型参数一起使用,比如:

inline fun <reified T> membersOf() = T::class.members
fun main(s: Array<String>)  println(membersOf<StringBuilder>().joinToString("\\n"))

注意:普通函数(未标记为inline)不能具有已具体化的参数。没有运行时表示形式的类型(例如:非具体化类型参数或虚设类型(如Nothing))不能用作具体化类型参数的参数。

内联属性(从1.1版本开始)

内联修饰符可用于没有支持字段的属性的访问器。你可以对单个属性访问器进行注释

val foo: Foo
inline get() = Foo()
var bar: Bar get() = ...
inline set(v)  ... 

你还可以注释整个属性,该属性将其两个访问器都标记为内联

inline var bar: Bar get() = ...
set(v)  ... 

在任何调用的地方,内联访问器将作为常规内联函数进行内联

协同程序

一些API执行耗时操作(如网络IO、文件IO、CPU或GPU密集型工作等),并要求调用者在完成之前进行阻塞。协程提供了一种避免阻塞线程的方法,并用一种更廉价、更可控的操作来代替它:暂停协程。
协同程序通过将复杂的内容放入库中来简化异步编程。程序的逻辑可以在协同程序中按顺序表达,底层库将为我们解决异步问题。库可以将用户代码的相关部分包装成回调、订阅相关事件、在不同线程(甚至不同的机器)上计划执行。它可以使代码保持简单,就好像是按顺序执行的一样。其他语言中可用的许多异步机制也可以使用Kotlin协程作为库实现。这包括来自C_和ECMAScript的异步/等待、来自Go的通道和选择以及来自C_和python的生成器/收益。

阻塞和协同的区别:

基本上,协同程序是可以挂起而不阻塞线程的计算。阻塞线程通常很昂贵,特别是在高负载下,因为只有相对较少的线程可以保持在周围,因此阻塞其中一个线程会导致一些重要的工作被延迟。
另一方面,协程悬挂几乎是自由的。不需要上下文切换或操作系统的任何其他参与。除此之外,暂停在很大程度上可以由用户库控制:作为库作者,我们可以决定暂停后会发生什么,并根据需要优化/记录/拦截。
另一个区别是协同程序不能被随机指令挂起,而只能在所谓的挂起点挂起,这些挂起点是对特殊标记函数的调用。

挂起函数

当我们调用用suspend修饰符标记的函数时,会发生暂停

suspend fun doSomething(foo: Foo): Bar  ...

这些函数被称为挂起函数,因为对它们的调用可能挂起协同程序(如果调用的结果已经可用,库可以决定不挂起而继续)。挂起函数可以采用与常规函数相同的方式获取参数和返回值,但只能从协程和其他挂起函数调用这些参数和返回值。实际上,要启动协同程序,必须至少有一个挂起函数,而且它通常是匿名的(即,它是一个挂起的lambda),比如:

fun <T> async(block: suspend () -> T)

这里,async()是一个常规函数(不是一个挂起函数),但是block参数有一个带有suspend修饰符的函数类型:suspend()->t。因此,当我们将lambda传递给async()时,它是一个挂起的lambda,并且我们可以调用暂停功能:

async 
doSomething(foo) 
...

同样的,await()可以是一个挂起的函数(因此也可以从异步块内调用),挂起协程,直到完成一些计算并返回其结果:

async  
...
val result = computation.await()
... 
    

注意,挂起的函数await()和dosomething()不能从像main()这样的常规函数调用:

fun main(args: Array<String>) 
doSomething() // ERROR: Suspending function called from a non-coroutine context

还要注意,挂起函数可以是虚拟的,当覆写它们时,必须指定挂起修饰符:

interface Base  suspend fun foo()

class Derived: Base 
override suspend fun foo()  ... 

@RestrictsSuspension 注解

XTension函数(和lambda)也可以标记为suspend,就像常规函数一样。这样就可以创建用户可以扩展的DSL和其他API。在某些情况下,库作者需要防止用户添加挂起协同程序的新方法。
为此,可以使用@restrictsuspension注解。当一个接收器类或接口R被@restrictsuspension注解时,所有挂起的扩展都需要委托给R的成员或其他扩展。由于扩展不能无限期地相互委托(程序不会终止),这就保证了通过调用R的成员(库的作者可以完全控制)来执行所有的暂停。

以上是关于kotlin函数的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin带接收者的lambda表达式

深入kotlin - 匿名函数闭包和接收者

Kotlin学习与实践 带接收者的lambda及Java的函数式接口

Kotlin系列之带接收者的Lambda

在 Kotlin DSL 中是不是可以完全避免函数名称?

Kotlin标准库函数 ③ ( with 标准库函数 | also 标准库函数 )