Kotlin学习之函数

函数声明

在kotlin中用关键字fun声明函数:

fun double(x:Int):Int{
}

其中Int是返回值类型,x指明参数类型是为Int

函数用法

通过传统方法调用函数:

 val result=double(2)

可以通过. 调用成员函数
Sample().foo()
Sample()是Sample类的一个实例

Infix符号

当满足如下条件的时候函数被中缀表示法调用:

  • 函数为成员函数或扩展函数
  • 函数只有一个参数
  • 函数用infix关键字标记

    //Int的扩展函数
    infix fun Int.shl(x:Int):Int{
    ...
    }
    //调用扩展函数可以用中缀表示法
    1 sh1 2//或者 1.she(2)
    

参数

函数的参数使用Pascal 命令法定义,形式:name:Type.参数之间用逗号隔开,每一个参数必须指明类型。

fun powerOf(number:Int,exponent:Int){
...
}

默认参数

函数的参数可以有默认值,当对应的参数被忽略的时候就使用默认值。
相对其它语言就少了一个重载的数量。

fun read(b:Array<Byte>,off:Int=0,len;Int=b.size()){
...
}

默认值定义在对应的Type后面。

命令参数

当调用函数的时候方法参数可以这命名,当函数有许多参数或有默认值时是很方便的。

fun reformat(str : String,normailizeCase :Boolean=true,upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' '){
...
}

我可以用默认值调用方法:

reformat(str)

当我们调用非默认的时候就要像这样:

reformat(str,true,true,false,'_')

用命令参数可以提高可读性

    reformat(str,normalizeCase=true,upperCaseFirstLetter = true,divideByCamelHumps = false,wordSeparator = '_'
)

如果我们不需要所有的参数

reformat(str,wordSeparator='_')

当调用java函数的时候不能使用命名参数语法,因为Java的字节码不能确保方法参数命名的不变性。

单一表示法

当一个函数使用单一表示法,花括号可以被省略,函数体定义在=后面

fun double(x:Int):Int=x*2

当返回类型可以由编译器推断时指定返回类型是可选的。

fun double(x:Int)=x*2

明确地指定返回类型

函数拥有代码块时必须明确地指出返回类型,如果是返回Unit这种情况就可以不用明确指定。当函数有代码块的时候kotlin是不能推断出返回类型的,因为这个时候代码里面有复杂的控制流,而且返回类型对与读者来说也不直观。

不定量的参数(Varargs)

函数的参数可以用vararg表示参数个数可变

fun <T>asList(vararg ts:T):List<T>{
 val result=ArrayList<T>()
 for(t in ts)
 result.add(t)
 return result
 }

可以这么调用:

val list=asList(1,2,3)

在函数内部T相当于是一个数组,在这个例子里ts变量是Array<out T>的引用

只有一个参数可以用vararg标记,如果不是最后一个参数被vararg标记的时候我们就要和命名参数法来赋值。或者如果参数是函数类型可以通过lambda表达式。

当我们调用一个vararg函数的时候,我们可以一个 一个地传递,就像asList(1,2,3),或者我已经有了一个现成的数组想把它传递给函数,我们可以用*前缀来引用

val a=arrayOf(1,2,3)
val list=asList(-1,0,*a,4)

函数作用域

在kotlin中函数可以定义为顶层函数,也就是说你不必要像java,C#,Scala之类的先定义一个class来将函数定义成成员函数。除了顶层函数之外,kotlin的函数类型还包括本地函数,像是成员函数和扩展函数。

本地函数

kotlin支持本地函数:在其它函数里面的函数。

fun dfs(graph: Graph) {
fun dfs(current: Vertex, visited: Set<Vertex>) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v, visited)
}
dfs(graph.vertices[0], HashSet())
}

一个本地函数可以访问本地外部函数的本地变量,所以在上面的例子,visited可以设置为本地变量。

fun dfs(graph: Graph) {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v)
}
dfs(graph.vertices[0])
}

成员函数

成员函数就是定义在一个类或对象里面的函数。

class Sample(){
fun foo(){print("Foo")}
}

成员函数的调用用.符号

Sample().foo()

泛型函数

在函数名称前用<>指定泛型参数的函数就叫泛型函数。

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

如果想多了解关于泛型函数去看看kotlin的泛型

尾递归函数

kotlin是支持尾递归函数的,这可以允许一些算法可以正常的使用循环而不是写一个递归函数,而且没有内存溢出的风险。如果一个函数用tailrec修饰符标记就满足了编译器优化递归的条件,并用高效迅速的循环代替它。

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

这段代码计算的是数学上的余弦不动点。Math.cos 从 1.0 开始不断重复,直到值不变为止,结果是 0.7390851332151607 这段代码和下面的是等效的:

private fun findFixPoint(): Double {
var x = 1.0
while (true) {
    val y = Math.cos(x)
    if ( x == y ) return y
    x = y
    }
}

使用 tailrec 修饰符必须在最后一个操作中调用自己。在递归调用代码后面是不允许有其它代码的,并且也不可以在 try/catch/finall 块中进行使用。当前的尾递归只在 JVM 的后端中可以用

高阶函数与 lambda 表达式

高阶函数就是可以接受函数作为参数返回一个函数的函数。比如 lock() 就是一个很好的例子,它接收一个 lock 对象和一个函数,运行函数并释放 lock

fun lock<T>(lock: Lock, body: () -> T ) : T {
lock.lock()
try {
    return body()
}
finally {
    lock.unlock()
}
}

现在解释一下上面的代码吧:body 有一个函数类型 () -> T,把它设想为没有参数并返回 T 类型的函数。它引发了内部的 try 函数块,并被 lock 保护,结果是通过 lock() 函数返回的。
如果我们想调用 lock() ,函数,我们可以传给它另一个函数做参数,参看函数参考:

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

其实最方便的办法是传递一个字面函数(通常是 lambda 表达式):

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

字面函数经常描述有更多细节,但为了继续本节,我们看一下更简单的预览吧:

  • 字面函数被包在大括号里
  • 参数在 -> 前面声明(参数类型可以省略)
  • 函数体在 -> 之后

在 kotlin 中有一个约定,如果最后一个参数是函数,可以定义在括号外面:

lock (lock) {
sharedResource.operation()
}

另外一个高阶函数的例子是 map() (of MapReduce):

    fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
val result = arrayListOf<R>()
for (item in this)
result.add(transform(item))
return result
}

可以被这样调用:

val doubled = ints.map { it -> it * 2 }

括号可以被完全忽略当参数只有一个函数的时候。
如果字面函数只有一个参数,则声明可以省略,名字就是 it :

ints map {it * 2}

这样就可以写LINQ-风格的代码了:

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

内联函数

使用高阶函数带来了相应的运行时麻烦:每个函数都是一个对象,它捕获闭包,即这些变量可以在函数体内被访问。内存的分配,虚拟调用的运行都会带来开销。
但在大多数这种开销是可以通过内联文本函数避免。下面就是一个很好的例子。lock() 函数可以在调用时很容易内联。思考一下下面的例子:

lock(l){
foo()
}

不是创建一个函数带有参数的函数而是产生一个调用,心编译器会编译出下面的代码:

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

这不是我们一开始想要的??
为了让编译器这么做,我们需要用inline修饰符来标记lock()

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

lnline修饰符会影响函数自身和传入lambda:它们都会在调用的时候内联。
内联会让生成的代码增加,但是如果我们可以合理的解决它(不要内联很大的函数),会有很多性能上的损耗。

@noinline

为了你想要一些 lambda 表达式传递给内联函数时是内联的,你可以给你的一些函数参数标记 @noinline 注解:

    inline fun foo(inlined: () -> Uint, @noinline notInlined: () -> Unit) {
//...
}

内联的 lambda 只能在内联函数中调用,或者作为内联参数,但 @noinline 标记的可以通过任何我们喜欢的方式操控:存储在字段,( 传递 等等)
注意如果内联函数没有内联的函数参数并且没有具体类型的参数,编译器会报警告,这样内联函数就没有什么优点的(如果你认为内联是必须的你可以忽略警告)

非本地返回

在 kotlin 中,我们可以不加条件的使用 return 去退出一个命名函数或表达式函数。这意味这退出一个 lambda 函数,我们不得不使用标签,而且空白的 return 在 lambda 函数中是禁止的,因为 lambda 函数不可以造一个闭合函数返回:

fun foo() {
ordinaryFunction {
    return // 错误 不可以在这返回
}
}

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

fun foo() {
inlineFunction {
    return //
}
}

注意有些内联函数可以调用传递进来的 lambda 函数,但不是在函数体,而是在另一个执行的上下文中,比如局部对象或者一个嵌套函数。在这样的情形中,非局部的控制流也不允许在lambda 函数中。为了表明,lambda 参数需要有 InlineOptions.ONLY_LOCAL_RETURN 注解:

        inline fun f(inlineOptions(InlineOption.ONLY_LOCAL_RETURN) body: () -> Unit) {
    val f = object: Runnable {
    override fun run() = body()
}
    // ...
}

内联 lambda 不允许用 break 或 continue ,但在以后的版本可能会支持。

实例化参数类型

有时候我们需要访问传递过来的类型作为参数:

    fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
var p = parent
while (p != null && !clazz.isInstance(p)) {
    p = p?.parent
}
@suppress("UNCHECKED_CAST")
return p as T
}

现在,我们创立了一颗树,并用反射检查它是否是某个特定类型。一切看起来很好,但调用点就很繁琐了:

myTree.findParentOfType(javaClass<MyTreeNodeType>() )

我们想要的仅仅是给这个函数传递一个类型,即像下面这样:

myTree.findParentOfType<MyTreeNodeType>()

为了达到这个目的,内联函数支持具体化的类型参数,因此我们可以写成这样:

inline fun &lt;reified T> TreeNode.findParentOfType(): T? {
var p = parent
while (p != null && p !is T) {
    p = p?.parent
}
return p as T
}

我们用 refied 修饰符检查类型参数,既然它可以在函数内部访问了,也就基本上接近普通函数了。因为函数是内联的,所以不许要反射,像 !is `as`这样的操作都可以使用。同时,我们也可以像上面那样调用它了

myTree.findParentOfType&lt;MyTreeNodeType>()

尽管在很多情况下会使用反射,我们仍然可以使用实例化的类型参数 javaClass() 来访问它:

inline fun methodsOf&lt;reified T>() = javaClass&lt;T>().getMethods()

fun main(s: Array&lt;String>) {
println(methodsOf&lt;String>().joinToString('\n'))
}

普通的函数(没有标记为内联的)不能有实例化参数。

匿名函数和函数表达示

匿名函数也就是没有声明的函数,但立即作为表达式传递下去。看看下面的例子:

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

max 函数就是一个高阶函数,它接受函数作为第二个参数。第二个参数是一个表达式所以本生就是一个函数,即字面函数。作为一个函数,相当于:

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

函数类型

一个函数要接受另一个函数作为参数,我们得给它指定一个类型。比如上面的 max 定义是这样的:

fun max<T>(collection: Collection<out T>, less: (T, T) -> Boolean): T? {
var max: T? = null
for (it in collection)
    if (max == null || less(max!!, it))
        max = it
return max
}

参数 less 是 (T, T) -> Boolean类型,也就是接受俩个 T 类型参数返回一个 Boolean:如果第一个参数小于第二个则返回真。
在函数体第四行, less 是用作函数
一个函数类型可以像上面那样写,也可有命名参数,更多参看命名参数

val compare: (x: T,y: T) -> Int = ...

Lambda表达式语法

lambda表达式的完全写法是下面这样的:

val sum = { x: Int, y: Int -> x + y }

lambda表达式由花括号包裹,类型注解是可选的,函数体是在 -> 之后,像下面这样:

val sum: (Int, Int) -> Int = {x, y -> x+y }

函数文本有时只有一个参数。如果 kotlin 可以从它本生计算出签名,那么可以省略这个唯一的参数,并会通过 it 隐式的声明它:

ints.filter {it > 0}//这是 (it: Int) -> Boolean  的字面意思

匿名函数

在使用lambda的情况下:在函数表达式中是能够指定函数的返回类型。在大多数时候是没有必要指定的因为返回类型可以自动识别,然而当你使用匿名函数的时候你必须指定:

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

一个匿名数据和常规的函数看起来非常像,除了名字被省略.它的函数体可以是一个表达式也可以是一个代码块:

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

它的参数和返回类型定义和常规的函数一样,除了这个参数被忽略了而这个参数可以被上下环境推断出来:

ints.filter(fun(item)=item>0)

匿名函数返回类型的推断和正常函数是一样的:
返回类型可以被推断出来当匿名函数只有一句表达式而当匿名函数有一个代码块的时候就必须指定他的返回类型了。
注意:通常匿名函数的参数从外面传递进来,这个缩写只允许函数括号外的参数只能在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)

匿名函数可以直接指定函数字面量的接收者类型。如果定义一个有接收者的函数类型的变量,就可以在稍后使用了:

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

当接收者类型可以从上下文推断出来的时候lambda表达式可以当成一个有接收者的函数字面量使用。

你可能感兴趣的:(Kotlin学习之函数)