Kotlin基础教程:函数及高阶函数和Lambda表达式

2019年9月6日05:48:14 发表评论 62 views

函数及Lambda表达式

函数声明
//普通完整方式
fun double(x:Int):Int{
    return 2*x
}
//函数表达式
fun double(x:Int):Int= 2*x
复制代码
参数

函数参数用Pascal表示法定义,即name:Type定义,参数用逗号隔开,每个参数都必须有显示类型

fun <T> joinToString(collection: Collection<T>, separator: String, prefix: String, postfix: String): String {
    /*....*/
}
复制代码
默认参数

函数参数可以有默认值,当省略相应的参数时使用默认值,可减少重载数量:

fun <T> joinToString(
    collection: Collection<T>,
    separator: String=" ", 
    prefix: String="[", 
    postfix: String="]"): String {
    /*....*/
}
复制代码

覆盖方法总是使用与基类类型方法相同的默认参数,当覆盖一个带有默认参数值的方法时,必须从签名中省略默认参数值

open class A {
	open fun foo(i: Int = 10) { /*……*/ }
}
class B : A() {
	 override fun foo(i: Int) { /*……*/ } // 不能有默认值
}
复制代码
命名参数

可在函数调用时使用命名的函数参数,当函数有大量的参数或默认参数时使用会方便:

fun reformat(str: String,
    normalizeCase: 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 = '_')
复制代码
可变参数Varrags

函数的参数(通常最后一个)可以用varag修饰符标记,与java...一致,但是使用时,如果需要传递的参数已经包装成数组,java可原样传递数组,而kotlin需要显示解包数组,使用展开运算符*:

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 a = arrayOf(1,2,3)
//需要显示展开
val list= asList(-1,0,*a,4)
复制代码
中缀表达式

标有 infix 关键字的函数也可以使⽤中缀表⽰法(忽略该调⽤的点与圆括号)调⽤。

中缀函数必须满⾜以下要求:

  • 它们必须是成员函数或扩展函数;
  • 它们必须只有⼀个参数;
  • 其参数不得接受可变数量的参数且不能有默认值。
infix fun Int.shl(x:Int):Int={/*...*/}
//中缀调用
1 shl 2
//等同:
1.shl(2)
复制代码

中缀调用优先级低于算术运算符、类型转换、及rangTo 操作符

1 shl 2+3 ==> 1 shl (2+3)
0 until n*2 ==> 0 util (n*2)
xs union ys as Set ==> xs union (ys as Set<*>)
复制代码

中缀调用优先级高于布尔操作符 && 、||、is、!is检测

a && b xor c ===> a && (b xor c)
a xoc b in c ===> (a xor b) in c
复制代码
函数作用域

Kotlin中函数可以在⽂件顶层声明顶层函数,Kotlin中函数也可以声明在局部作⽤域、作为成员函数 以及扩展函数。

局部函数

Koltin支持局部函数,即在一个函数内声明一个另一个函数

fun dfs(graph: Graph) {
    fun dfs(current: Vertex, visited: MutableSet<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])
}
复制代码

Lambda表达式

Lambda表达式语法
var sum = { x: Int, y: Int -> x + y }
复制代码
  • lambda表达式始终用花括号包围,实参并没有用括号括起来,箭头把参数列表和lambda函数体隔开。
  • lambda表达式是函数调用的最后一个实参,可以放到括号的外边。
    people.maxby{ it.age }
    复制代码
  • 如果想传递两个或更多的lambda,不能把超过一个的lambda放到外边。
Lambda表达式变量捕获

如果在函数内使用lambda表达式,可以访问这个函数的参数及定义在lambda表达式之前的局部变量。

fun printMessagesWithPrefix(messages: Collection<String>, prefix: String) {
    var count = 0
    messages.forEach {
        count++
        println("$prefix $it $count")
    }
}
复制代码

kotlinJava一个显著的区别就是,Kotlin中不会仅限于final变量的访问,还可以在lamda内部修改这些变量

成员引用

fun salute() = println("Salute!")
data class Person(val name: String, val age: Int)
fun Person.isAdult(age: Int):Boolean {
    return this.age >= 18
}
fun main(args: Array<String>) {
    //引用顶成函数
    run(::salute)
    //构造方法引用
    val createPerson = ::Person
    val p = createPerson("Alice", 29)
    println(p)

    listOf<Person>().forEach {
        //成员引用
        println(Person::age)
        //扩展函数引用
        println(Person::isAdult)
    }  
}

复制代码

高阶函数

高阶函数就是另一个函数作为参数和返回值的函数。如

public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}
复制代码
函数类型

声明函数类型,需要将函数参数类型放在括号中,紧接着是一个箭头和函数的返回类型:

(Int,String)->Unit
//声明一个函数变量
val sum: (Int, Int) -> Int = { x, y -> x + y }
复制代码

函数的返回可以为null,或者定义一个函数类型的可空变量

var canReturnNull: (Int, Int) -> Int? = { _, _ -> null }

var funOrNull: ((Int, Int) -> Int)? = null
复制代码
函数的调用

函数类型的值可以通过其 invoke 操作符调⽤:f.invoke(x) 或者直接f(x)。 如果该值具有接收者类型,那么应该将接收者对象作为第⼀个参数传递。 调⽤带有接收者的函数类型值 的另⼀个⽅式是在其前⾯加上接收者对象, 就好⽐该值是⼀个扩展函数:1.foo(2)

fun <T> Collection<T>.joinToString(
        separator: String = ", ",
        prefix: String = "",
        postfix: String = "",
        transform: ((T) -> String)? = null
): String {
    val result = StringBuilder(prefix)
    for ((index, element) in this.withIndex()) {
        if (index > 0) result.append(separator)
        val str = transform?.invoke(element)
            ?: element.toString()
        result.append(str)
    }
    result.append(postfix)
    return result.toString()
}
复制代码
Java中使用函数类

函数类型被声明为普通的接口:一个函数类型的变量是FunctionN接口的实现。Kotlin定义了一系列的接口,这些接口对应不同参数的函数:

  • Function0<R>: 无参函数
  • Fucntion1<P1,R>:一个参数的函数

每个接口都定义了一个invoke方法,调用这个方法就会执行函数

fun processTheAnswer(f: (Int) -> Int) {
    println(f(42))
}
复制代码
public static void main(String[] args) {
        processTheAnswer(
            new Function1<Integer, Integer>() {
                @Override
                public Integer invoke(Integer number) {
                    System.out.println(number);
                    return number + 1;
                }
        });
}
复制代码
返回函数的函数
class ContactListFilters {
    var prefix: String = ""
    var onlyWithPhoneNumber: Boolean = false
    fun getPredicate(): (Person) -> Boolean {
        val startsWithPrefix = { p: Person ->
            p.firstName.startsWith(prefix) || p.lastName.startsWith(prefix)
        }
        if (!onlyWithPhoneNumber) {
            return startsWithPrefix
        }
        return { startsWithPrefix(it) && it.phoneNumber != null }
    }
}
复制代码

内联函数

使用高阶函数(lambda表达式)会被正常编译成匿名类,每次调用一次lambda表达式会创建一个额外的类,如果lambda表达式捕获了变量,每次调用就会创建一个新的对象。内存分配(对于函数对象和类)和虚拟调⽤会引⼊运⾏时间开销。

当一个函数被声明为inline时,他的函数体是内联的,函数体会被直接替换到函数调用的地方,而非正常调用。


inline fun <T> synchronized(lock: Lock, action: () -> T): T {
    lock.lock()
    try {
        return action()
    } finally {
        lock.unlock()
    }
}
//调用内联函数
fun foo(lock: Lock){
    println("Before Sync")
    synchronized(1){
        println("Action")
    }
    println("After sync")
}
//相同的代码,被编译后:

fun _foo_(lock: Lock){
    println("Before Sync")
    lock.lock()
    try {
        println("Action")
    } finally {
        lock.unlock()
    }
    println("After sync")
}
复制代码
禁用内联

如果希望只内联⼀部分传给内联函数的 lambda 表达式参数,那么可以⽤ noinline修饰符标记不希 望内联的函数参数:

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { …… }
复制代码

可以内联的 lambda 表达式只能在内联函数内部调⽤或者作为可内联的参数传递, 但是 noinline的可以以任何我们喜欢的⽅式操作:存储在字段中、传送它等等。

非局部返回

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

fun foo() {
    //非内联函数
    ordinaryFunction {
    	return // 错误:不能使 `foo` 在此处返回
	}
}
复制代码

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

inline fun inlined(block: () -> Unit) { println("hi!") }
fun foo() {
    inlined {
    	return // OK:该 lambda 表达式是内联的
    }
}	
//局部返回可以使用匿名函数或标签
fun foo2() {
    inlined(fun() {
        /*...*/
        return //lamda表达式返回
    })
}  
复制代码

这种返回(位于 lambda 表达式中,但退出包含它的函数)称为⾮局部返回。

⼀些内联函数可能调⽤传给它们的不是直接来⾃函数体、⽽是来⾃另⼀个执⾏上下⽂的lambda 表达式参数,例如来⾃局部对象或嵌套函数。在这种情况下,该 lambda 表达式中也不允许⾮局部控制流。为了标识这种情况,该 lambda 表达式参数需要⽤ crossinline 修饰符标记.

带接收者的高阶函数
  • with函数:将第一个参数转换成作为第二个参数传给他的lambda表达式的接受者。返回值为lambda表达式最后的一行结果。
    fun alphabet(): String {
        val stringBuilder = StringBuilder()
        return with(stringBuilder) {
            for (letter in 'A'..'Z') {
                this.append(letter)
            }
            append("\nNow I know the alphabet!")
            this.toString()
        }
    }
    复制代码
  • apply函数:和with函数差不多,但是返回值为接收者对象,即第一个参数。
    fun alphabet() = StringBuilder().apply {
        for (letter in 'A'..'Z') {
            append(letter)
        }
        append("\nNow I know the alphabet!")
    }.toString()
    复制代码

作者:showdy
链接:https://juejin.im/post/5d6f11fff265da03f66ddeb1
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: