Kotlin中的函数

中缀表示法

暂时还体会不到infix的妙处!?

函数还可以用中缀表示法调用,当他们是成员函数或扩展函数,只有一个参数,用 infix关键字标注

// 给 Int 定义扩展
infix fun Int.shl(x: Int): Int {
……
}
// 用中缀表示法调用扩展函数
1 shl 2
// 等同于这样
1.shl(2)

可变数量的参数和星操作符

可变参数vararg

使用关键字vararg来修饰可变参数。

/*
在函数内部,类型 T 的 vararg 参数的可见方式是作为 T 数组,即上例中的 ts 变量具有类型 Array <out T>。
*/
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
}

因为有命名参数传递法可以直接在函数后面加括号传递lamba两种方式,其他的和java中的可变参数一致。

星操作符

我们可以使用伸张(spread)操作符*,来直接传入一个数组。

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

内联函数

内联函数,我的理解就是想xml中的<include></include>标签一样。

例子如下: 在Android开发中,打印信息

//通过关键字inline来定义内联函数。 
//泛型中的 reified关键字告诉编译器,这个泛型是可以具体拿到值的泛型。(不是只是在编译器做检查的)
inline fun <reified T> T.debug(log:Any)
{
    Log.d(T::class.simpleName, log.toString())
}

具体化的类型参数

具体化的参数类型,就是指使用reified关键字。由于函数是内联的。故可以当做类型本身来使用它。并且不是反射! 对比:

//没有使用reified和内联
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?
}
//使用了内联和reified
inline fun <reified T> TreeNode.findParentOfType(): T? {
    var p = parent
    //可以直接使用is as 来处理。
    while (p != null && p !is T) {
        p = p.parent
    }
    return p as T?
}

禁止内联

这部分还是不大了解。这里等待后面具体来看什么时候非内联

如果你只想被(作为参数)传给一个内联函数的 lamda 表达式中只有一些被内联,你可以用 noinline 修饰符标记一些函数参数:

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

可以内联的 lambda 表达式只能在内联函数内部调用或者作为可内联的参数传递, 但是 noinline 的可以以任何我们喜欢的方式操作:存储在字段中、传送它等等。 需要注意的是,如果一个内联函数没有可内联的函数参数并且没有具体化的类型参数,编译器会产生一个警告,因为内联这样的函数很可能并无益处(如果你确认需要内联,则可以关掉该警告)。

非局部返回

这部分还是不大了解。这里等待后面具体来看什么时候非内联

在 Kotlin 中,我们可以只使用一个正常的、非限定的 return 来退出一个命名或匿名函数。 这意味着要退出一个 lambda 表达式,我们必须使用一个标签,并且在 lambda 表达式内部禁止使用裸 return,因为 lambda 表达式不能使包含它的函数返回:

fun foo() {
    ordinaryFunction {
        return // 错误:不能使 `foo` 在此处返回
    }
}

但是如果 lambda 表达式传给的函数是内联的,该 return 也可以内联,所以它是允许的:

fun foo() {
    inlineFunction {
        return // OK:该 lambda 表达式是内联的
    }
}

这种返回(位于 lambda 表达式中,但退出包含它的函数)称为非局部返回。 我们习惯了在循环中用这种结构,其内联函数通常包含:

fun hasZeros(ints: List<Int>): Boolean {
    ints.forEach {
        if (it == 0) return true // 从 hasZeros 返回
    }
    return false
}

这里看源码就会发现。其实foreach是使用内联修饰的!!!

请注意,一些内联函数可能调用传给它们的不是直接来自函数体、而是来自另一个执行上下文的 lambda 表达式参数,例如来自局部对象或嵌套函数。在这种情况下,该 lambda 表达式中也不允许非局部控制流。为了标识这种情况,该 lambda 表达式参数需要用 crossinline 修饰符标记:

//crossinline 是不许用函数中有返回的?
inline fun f(crossinline body: () -> Unit) {
    val f = object: Runnable {
        override fun run() = body()
    }
    // ……
}

单表达式函数

如果一个函数只有一个表达式,则可以直接写"="

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

局部函数

Kotlin 支持局部函数,即一个函数在另一个函数内部。局部函数可以访问外部函数(闭包)的参数。

fun dfs(graph: Graph) {
    //visited 是函数中的变量
    val visited = HashSet<Vertex>()
    fun dfs(current: Vertex) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v)
    }

    dfs(graph.vertices[0])
}

高阶函数

尾递归函数

暂时不明所以,跳过。。。

高阶函数

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

Lambda 表达式

  • 简述
    • lambda 表达式总是被大括号括着,
    • 其参数(如果有的话)在 -> 之前声明(参数类型可以省略),
    • 函数体(如果存在的话)在 -> 后面。

在 Kotlin 中有一个约定,如果函数的最后一个参数是一个函数,并且你传递一个 lambda 表达式作为相应的参数,你可以在圆括号之外指定它

     lock (lock) {
      sharedResource.operation()
     }
     //高阶函数的另一个例子是 map():
     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 { value -> value * 2 }
     //请注意,如果 lambda 是该调用的唯一参数,则调用中的圆括号可以完全省略。

函数字面量

一个 lambda 表达式或匿名函数是一个“函数字面值”,即一个未声明的函数, 但立即做为表达式传递。考虑下面的例子:

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

函数 max 是一个高阶函数,换句话说它接受一个函数作为第二个参数。 其第二个参数是一个表达式,它本身是一个函数,即函数字面值。写成函数的话,它相当于

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

函数类型

对于接受另一个函数作为参数的函数,我们必须为该参数指定函数类型。 例如上述函数 max 定义如下:

fun <T> max(collection: Collection<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的参数并返回一个布尔值的函数: 如果第一个参数小于第二个那么该函数返回 true。 在上面第 4 行代码中,less 作为一个函数使用:通过传入两个 T 类型的参数来调用。 如上所写的是就函数类型,或者可以有命名参数,如果你想文档化每个参数的含义的话。

val compare: (x: T, y: T) -> Int = ……
//如要声明一个函数类型的可空变量,请将整个函数类型括在括号中并在其后加上问号:
var sum: ((Int, Int) -> Int)? = null

Lambda 表达式语法

Lambda 表达式的完整语法形式,即函数类型的字面值如下:

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

lambda 表达式总是被大括号括着, 完整语法形式的参数声明放在括号内,并有可选的类型标注, 函数体跟在一个 -> 符号之后。如果推断出的该 lambda 的返回类型不是 Unit,那么该 lambda 主体中的最后一个(或可能是单个)表达式会视为返回值。

//如果我们把所有可选标注都留下,看起来如下:
val sum: (Int, Int) -> Int = { x, y -> x + y }
//一个 lambda 表达式只有一个参数是很常见的。 如果 Kotlin 可以自己计算出签名,它允许我们不声明唯一的参数,并且将隐含地为我们声明其名称为 it:
ints.filter { it > 0 } 
// 这个字面值是“(it: Int) -> Boolean”类型的

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

ints.filter {
    val shouldFilter = it > 0 
    shouldFilter
}

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

请注意,如果一个函数接受另一个函数作为最后一个参数,lambda 表达式参数可以在圆括号参数列表之外传递。 参见 callSuffix 的语法。

匿名函数(与其区别)

返回类型可以自动推断出来。然而,**如果确实需要显式指定,可以使用另一种语法: 匿名函数 **

fun(x: Int, y: Int): Int = x + y
//匿名函数看起来非常像一个常规函数声明,除了其名称省略了。其函数体可以是表达式(如上所示)或代码块:
fun(x: Int, y: Int): Int {
    return x + y
}
//参数和返回类型的指定方式与常规函数相同,除了能够从上下文推断出的参数类型可以省略:
ints.filter(fun(item) = item > 0)
  • 匿名函数的返回类型推断机制与正常函数一样: 对于具有表达式函数体的匿名函数将自动推断返回类型,而具有代码块函数体的返回类型必须显式指定(或者已假定为 Unit)。 请注意,匿名函数参数总是在括号内传递。 允许将函数留在圆括号外的简写语法仅适用于 lambda 表达式。
  • Lambda表达式和匿名函数之间的另一个区别是非局部返回的行为。 一个不带标签的 return 语句总是在用 fun 关键字声明的函数中返回。这意味着 lambda 表达式中的 return 将从包含它的函数返回,而匿名函数中的 return 将从匿名函数自身返回。

闭包

Lambda 表达式或者匿名函数(以及局部函数和对象表达式) 可以访问其 闭包 ,即在外部作用域中声明的变量。 与 Java 不同的是可以修改闭包中捕获的变量

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

带接收者的函数字面值

这个最好玩了。可以实现dsl风格的代码全靠它

Kotlin 提供了使用指定的 接收者对象 调用函数字面值的功能。 可以调用该接收者对象上的方法而无需任何额外的限定符,可以任意调用接受者的方法和属性。其用法的最重要的示例之一就是类型类型安全的Groovy-风格构建器

//这样的函数字面值的类型是一个带有接收者的函数类型:
//下面是定义参数
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()  // 创建接收者对象
    html.init()        // 将该接收者对象传给该 lambda
    return html
}


//后面这个是看明白了。可是前面的还是看不明白!!
html {       // 带接收者的 lambda 由此开始
    body()   // 调用该接收者对象的一个方法
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏一个会写诗的程序员的博客

Integer的自动拆装箱的陷阱(整型数-128到127的值比较问题)

Java里面对处在在-128127之间的Integer值,用的是原生数据类型int,会在内存里供重用,也就是说这之间的Integer值进行==比较时只是进行in...

17710
来自专栏DannyHoo的专栏

为什么NSString要用Copy来修饰?

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010105969/article/details/...

19020
来自专栏码云1024

JAVA 第二天 关键字

29870
来自专栏小樱的经验随笔

【Java学习笔记之七】java函数的语法规则总结

函数的概述 发现不断进行加法运算,为了提高代码的复用性,就把该功能独立封装成一段独立的小程序,当下次需要执行加法运算的时候,就可以直接调用这个段小程序即可,那么...

33760
来自专栏java初学

final关键字

392120
来自专栏三流程序员的挣扎

Python 自学二——类型

', '.join(['cats', 'rats', 'bats']) # 以 `, ` 分隔 'cats, rats, bats' s = 'cat ra...

18030
来自专栏编程

Python面向对象1:基础介绍+封装特征

目前有三种编程方式: 面向过程:根据业务逻辑从上到下写垒代码 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 面向对象:对函数进行分类和封...

21770
来自专栏Pythonista

python内置函数大全

最近一直在看python的document,打算在基础方面重点看一下python的keyword、Build-in Function、Build-in Cons...

22910
来自专栏python3

python 集合

说明: 拿list_1每一个元素去list_2中查找,如果有,直接忽略,否则就直接输出。

18920
来自专栏深度学习思考者

C++常见问题(二)——虚函数、类与结构、引用与值传递

一 文件输入输出的方式 C++定义了ifsteam、ofstream和fstream 3种类型以用来支持文件的输入输出。 二 异常 异常就是程序运行时出...

19760

扫码关注云+社区

领取腾讯云代金券