本文是对<<Kotlin in Action>>
的学习笔记,若是须要运行相应的代码能够访问在线环境 try.kotlinlang.org,这部分的思惟导图为: java
lambda
,这一章咱们将学到如何建立
高阶函数:使用
lambda
做为
参数或者返回值 的函数。高阶函数有助于简化代码,去除代码重复,以及构建漂亮的抽象概念。
按照定义,高阶函数就是 以另外一个函数做为参数或者返回值的函数,在Kotlin
中,函数能够用lambda
或者函数引用来表示。例如,标准库中的filter
函数将一个判断式函数做为参数,所以它就是一个高阶函数。设计模式
list.filter { x > 0 }
复制代码
为了声明一个以lambda
做为实参的函数,你须要知道如何声明 对应形参的类型。下面咱们先看一个简单的例子,把lambda
表达式保存在局部变量当中:安全
val sum = { x : Int, y : Int -> x + y }
val action = { println(42) }
复制代码
在上面的例子中,咱们省去了类型的声明。可是编译器能够推导出sum
和action
这两个 变量具备函数类型,这些变量的显示声明为:app
//有两个 Int 型参数和 Int 型返回值的函数
val sum : (Int, Int) -> Int = {x, y -> x + y}
//没有参数和返回值的函数
val action : () -> Unit = { println(42) }
复制代码
声明函数类型,须要 将函数参数类型放在括号中,紧接着是一个箭头和函数的返回类型:ide
(Int, String) -> Unit
复制代码
Unit
类型用于表示函数不返回任何有用的值,在声明一个普通的函数时,Unit
类型的返回值是能够忽略的,可是一个 函数类型声明老是须要一个显示的返回类型,因此在这种场景下Unit
是不能省略的。函数
在{x, y -> x + y}
中,由于它们的类型已经在函数类型的变量声明部分指定了,不须要在lambda
当中重复声明。post
就像其它方法同样,函数类型的返回值也能够标记为可空类型:学习
var canReturnNull : (Int, Int) -> Int? = { null }
复制代码
也能够定义一个 函数类型的可空变量,为了明确表示 变量自己可空,而不是函数类型的返回类型可空,你须要 将整个函数类型的定义包含在括号内并在括号后添加一个问号:网站
var funOrNull : ((Int, Int) -> Int)? = null
复制代码
能够为函数类型声明中的参数指定名字:ui
//函数类型的参数如今有了名字...
fun performRequest(url : String, callback : (code : Int, content : String) -> Unit) {
//....
}
复制代码
调用方法为:
>> val url = "http://kotl.in"
//可使用 API 中提供的参数名字做为 lambda 参数的名字....
>> performRequest(url) { code, content -> / *...* / }
>> performRequest(url) { code, page -> / *...* / }
复制代码
参数名称不会影响类型的匹配,当你声明一个lambda
时,没必要使用和函数类型声明中如出一辙的参数名称,但命名会提高代码的可读性而且能用于IDE
的代码补全。
下面咱们讨论如何实现一个高阶函数,这个例子会尽可能简单而且使用以前的lambda sum
一样的声明,这个函数实现对于两个整数的任意操做,而后打印出结果:
fun twoAndThree(operation: (Int, Int) -> Int) {
val result = operation(2, 3)
println("The result is $result")
}
fun main(args: Array<String>) {
twoAndThree { a, b -> a + b }
twoAndThree { a, b -> a * b }
}
复制代码
运行结果为:
>> The result is 5
>> The result is 6
复制代码
调用做为参数的函数operation
和调用普通函数的语法是同样:把括号放在函数名后,并把参数放在括号内。下面,让咱们实现一个标准的库函数:filter
函数。它会过滤掉字符串中不属于a..z
范围内的字母。
fun String.filter(predicate: (Char) -> Boolean): String {
val sb = StringBuilder()
for (index in 0 until length) {
val element = get(index)
if (predicate(element)) sb.append(element)
}
return sb.toString()
}
复制代码
filter
函数以一个判断式做为参数,判断式的类型是一个函数,以字符串做为参数并返回boolean
类型的值。
fun main(args: Array<String>) {
println("ab1c".filter { it in 'a'..'z' })
}
复制代码
运行结果:
>> abc
复制代码
背后的原理是:
FunctionN
接口的一个实现。Kotlin
标准库定义了一系列的接口:Function0<R>
表示没有参数的函数,Function1<P1, R>
表示一个参数的函数。Function
接口的实现类的实例,每一个接口定义了一个invoke
方法,实现类的invoke
方法包含了lambda
函数体,调用这个方法就会执行函数。在Java
中能够很简单地调用使用了函数类型的Kotlin
函数,Java 8
的lambda
会被自动转换为函数类型的值:
//Kotlin 声明
fun processTheAnswer(f : (Int) -> Int) {
println(f(42))
}
复制代码
//Java
processTheAnswer(number -> number + 1)
复制代码
在旧版的Java
中,能够传递一个实现了函数接口中的invoke
方法的匿名内部类的实例:
>> processTheAnswer(
new Function1<Integer, Integer>() {
@override
public Integer invoke(Integer number) {
System.out.println(number);
return number + 1;
}
}
)
复制代码
在Java
中能够很容易地使用Kotlin
标准库中以lambda
做为参数的扩展函数,可是必需要 显示地传递一个接收者做为第一个参数:
List<String> strings = new ArrayList();
strings.add("42");
CollectionsKt.forEach(strings, s -> {
System.out.println(s);
retrun Unit.INSTANCE;
});
复制代码
在Java
中,函数或者lambda
能够返回Unit
。但由于在Kotlin
中Unit
类型是有一个值的,因此须要显示地返回它。一个返回void
的lambda
不能做为返回Unit
的函数类型的实参,就像以前的例子中的(String) -> Unit
。
以joinToString
函数为例,咱们除了能够定义前缀、后缀和分隔符之外,还能够经过最后一个 函数类型的参数 指定如何将集合当中的每一个元素转换成为String
,这是一个泛型函数:它有一个类型参数T
表示集合中的元素的类型,Lambda transform
将接收这个类型的参数,下面咱们来看一下如何为它指定一个lambda
做为默认值:
fun <T> Collection<T>.joinToString(
separator: String = ", ",
prefix: String = "",
postfix: String = "",
//为函数类型的参数提供默认值。
transform: (T) -> String = { it.toString() }
): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(separator)
//调用传入的函数。
result.append(transform(element))
}
result.append(postfix)
return result.toString()
}
fun main(args: Array<String>) {
val letters = listOf("Alpha", "Beta")
println(letters.joinToString())
println(letters.joinToString { it.toLowerCase() })
println(letters.joinToString(separator = "! ", postfix = "! ",
transform = { it.toUpperCase() }))
}
复制代码
运行结果为:
>> Alpha, Beta
>> alpha, beta
>> ALPHA! BETA!
复制代码
当声明一个参数为可空的函数类型时,不能直接调用做为参数传递进来的函数:Kotlin
会由于检测到潜在的空指针而致使编译失败,在这种状况下有两种处理方式:
null
显示地检查null
是一种比较容易理解的方法:fun foo(callback : (() _ Unit)?) {
if (callback != null) {
callback()
}
}
复制代码
invoke
方法的接口的具体实现,做为一个普通方法,invoke
能够经过安全调用语法调用:callback?.invoke() ?: /* 默认实现 */
复制代码
从函数中返回另外一个函数适用于下面的场景:程序中的一段逻辑可能会由于程序的状态或者其余条件而产生变化,好比说下面的例子,运输费用的计算依赖于选择的运输方式:
//声明一个枚举类型。
enum class Delivery { STANDARD, EXPIRED }
class Order(val itemCount : Int)
//返回的函数类型为:形参为 Order 类,返回类型为 Double。
fun getShippingCalculator(delivery : Delivery) : (Order) -> Double {
if (delivery == Delivery.EXPIRED) {
return { order -> 6 + 2.1 * order.itemCount }
}
return { order -> 1.2 * order.itemCount }
}
fun main(args: Array<String>) {
val calculator = getShippingCalculator(Delivery.EXPIRED)
println("cost ${calculator(Order(3))}")
}
复制代码
在上面的例子中,getShippingCalculator
返回了一个函数,这个函数以Order
做为参数并返回一个Double
类型的值,要返回一个函数,须要写一个return
表达式,跟上一个lambda
、一个成员引用,或者其余的函数类型的表达式。
下面,咱们来看一个过滤器的例子:
data class Person(val firstName : String, val phoneNumber : String?) class ContactListFilter {
var prefix : String = ""
var onlyWithPhoneNumber : Boolean = false fun getPredicate() : (Person) -> Boolean {
val startWithPrefix = { p : Person ->
p.firstName.startsWith(prefix)
}
if (!onlyWithPhoneNumber) {
return startWithPrefix
}
return { startWithPrefix(it) && it.phoneNumber != null }
}
}
fun main(args: Array<String>) {
val contacts = listOf(Person("Dmitry", "123-4567"),
Person("Svelana", null))
val contactListFilters = ContactListFilter()
contactListFilters.prefix = "S"
contactListFilters.onlyWithPhoneNumber = false println(contacts.filter(contactListFilters.getPredicate())) } 复制代码
运行结果为:
>> [Person(firstName=Svelana, phoneNumber=null)]
复制代码
咱们来看一个分析网站的例子,SiteVisit
类用来保存每次访问的路径、持续时间和用户的操做系统。
data class SiteVisit( val path: String, val duration: Double, val os: OS ) enum class OS { WINDOWS, LINUX, MAC, IOS, ANDROID }
val log2 = listOf(
SiteVisit("/", 34.0, OS.WINDOWS),
SiteVisit("/", 22.0, OS.MAC),
SiteVisit("/login", 12.0, OS.WINDOWS),
SiteVisit("/signup", 8.0, OS.IOS),
SiteVisit("/", 16.3, OS.ANDROID)
)
复制代码
接下来,咱们经过扩展函数的方式,定义一个方法用于统计 符合特定条件 的操做系统用户的平均使用时长。
fun List<SiteVisit>.averageDuration(predicate : (SiteVisit) -> Boolean) =
filter(predicate).map(SiteVisit::duration).average()
复制代码
运行下面的代码:
fun main(args: Array<String>) {
println(log2.averageDuration {it.os in setOf(OS.WINDOWS, OS.ANDROID) }) } 复制代码
对于一些广为人知的设计模式可使用函数类型和lambda
表达式进行简化,好比策略模式。没有lambda
表达式的状况下,你须要声明一个接口,并为每一种可能的策略提供实现类。使用函数类型,能够用一个通用的函数类型来描述策略,而后传递不一样的lambda
表达式做为不一样的策略。