本章内容包括:
- 函数类型
- 离阶函数及其在组织代码过程中的应用
- 内联函数
- 非局部返回和标签
- 重名函数
8.1 声明高阶函数
// 高阶函数就是以另一个函数作为参数或者返回值的函数。
val list = listOf(0, 1, 2, 3)
println(list.filter { it > 0 })
/**-------------------- 8.1.1 函数类型 ----------------------*/
// Kotlin的类型推导
val sum = { x: Int, y: Int -> x + y }
val action = { println(42) }
// 这些变量的显示类型声明是什么样的?
// 有两个Int型参数和Int型返回值的函数
val sum2: (Int, Int) -> Int = { x, y -> x + y }
// 没有参数和返回值的函数
val action2: () -> Unit = { println(42) }
/*
* (Int, String) -> Unit
* 参数类型 返回类型
* 声明函数类型,需要将函数参数类型防在括号中,紧接着是一个箭头和函数的返回类型
*/
// 标记函数类型 返回值为可空 类型:
var canReturnNull: (Int, Int) -> Int? = { x, y -> null }
// 标记 函数类型的可空 变量
var funOrNull: ((Int, Int) -> Int)? = null
// 函数类型的参数名:可以为函数类型声明中的参数指定名字:
fun performRequest(url: String, callback: (code: Int, content: String) -> Unit) {
// 函数类型的参数现在有了名字
println("performRequest")
}
val url = "http://www.jinbeen.com"
// 可以使用API中提供的参数名字作为lambda参数的名字
performRequest(url, { code, content -> { println(url.split('.')) } })
performRequest(url) { code, content -> { println(url.split('.')) } }
// 或者你可以改变参数的名字
performRequest(url) { code, page -> { println(url.split('.')) } }
/**-------------------- 8.1.2 调用作为参数的函数 ----------------------*/
// 代码清单8.1 定义一个简单高阶函数
// 定义一个函数类型的参数
fun twoAndThree(operation: (Int, Int) -> Int) {
// 调用函数类型的参数
val result = operation(2, 3)
println("The result is $result")
}
twoAndThree { a, b -> a + b }// The result is 5
twoAndThree { a, b -> a * b }// The result is 6
/*
* filter 函数的声明,以一个判断式作为参数
*
* 接收者类型 参数类型 函数类型参数
* fun String.filter(predicate: (Char)->Boolean): String
* Char: 作为参数传递的函数的参数类型
* Boolean: 作为参数传递的函数的返回类型
*/
// 代码清单8.2 实现一个简单版本的filter函数
// 检查每个字符是否满足判断式,如果满足就将字符添加到包含结果的 StringBuilder 中
fun String.filter(predicate: (Char) -> Boolean): String {
val sb = StringBuilder()
for (index in 0 until length) {
val element = get(index)
// 调用作为参数传递给 predicate 的函数
if (predicate(element)) sb.append(element)
}
return sb.toString()
}
// 传递一个lambda作为 predicate 参数
println("ab1c".filter { it in 'a'..'z' })// abc
/**-------------------- 8.1.3 在Java中使用函数 ----------------------*/
/*kotlin类型的声明*/
fun processTheAnswer(f: (Int) -> Int) {
println(f(42))
}
/*Java8*/
// processTheAnswer(number->number+1)
/*旧版的Java*/
// processTheAnswer(new Function1 < Integer, Integer > (){
// @Override
// public Integer invoke(Integer number){
// System.out.println(number);
// return number+1;
// }
// });
/**-------------------- 8.1.4 函数类型的参数默认值和null值 ----------------------*/
// 代码清单8.3 使用了硬编码toString转换的joinToString函数
fun <T> Collection<T>.joinToString(
separator: String = ",",
prefix: String = "",
postfix: String = ""
): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(separator)
// 使用默认的 toString 方法将对象转换为字符串
result.append(element)
}
result.append(postfix)
return result.toString()
}
// 代码清单8.4 给函数类型的参数指定默认值
fun <T> Collection<T>.joinToString2(
separator: String = ", ",
prefix: String = "",
postfix: String = "",
// 声明一个以lambda为默认值的函数类型的参数
transForm: (T) -> String = { it.toString() }
): String {
val result = StringBuilder(prefix)
for ((index, element) in this.withIndex()) {
if (index > 0) result.append(separator)
// 调用作为实参传递给 transform 形参的函数
result.append(transForm(element))
}
result.append(postfix)
return result.toString()
}
val letters = listOf("jingbin", "Jinbeen")
// 使用默认的转换函数
println(letters.joinToString2())// jingbin, Jinbeen
// 传递一个lambda作为参数
println(letters.joinToString2 { it.toLowerCase() })// jingbin, jinbeen
// 使用命名参数语法传递几个参数,包括一个lambda
println(letters.joinToString2("! ", "! ",
" ", transForm = { it.toUpperCase() }))// JINGBIN! JINBEEN
fun foo(callback: (() -> Unit)?) {
// 显示的检查null
if (callback != null) {
callback()
}
// 也可以这样写
callback?.invoke()
}
// 代码清单8.5 使用函数类型的可空参数
fun <T> Collection<T>.jointToString3(
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)
// 使用安全调用语法调用函数
// 使用Elvis运算符处理回调没有被指定的情况
val str = transForm?.invoke(element) ?: element.toString()
result.append(str)
}
result.append(postfix)
return result.toString()
}
/**-------------------- 8.1.5 返回函数的函数 ----------------------*/
// 代码清单8.6 定义一个返回函数的函数
// enum class Delivery { STANDARD, EXPEDITED }
class Order(val itemCount: Int)
fun getShippingCostCalculator(
// 声明一个返回函数的函数
delivery: Delivery): (Order) -> Double {
if (delivery == Delivery.EXPEDITED) {
// 返回lambda
return { order -> 6 + 2.1 * order.itemCount }
}
// 返回lambda
return { order -> 1.2 * order.itemCount }
}
// 取得的是函数,将返回的函数保存在变量中
val calculator = getShippingCostCalculator(Delivery.EXPEDITED)
println("Shopping costs ${calculator(Order(3))}")// 12.3
/*
* 声明一个返回另一个函数的函数,需要指定一个函数类型作为返回类型。
* getShippingCostCalculator返回了一个函数,这个函数以 Order 作为参数并返回一个 Double 类型的值。
* 要返回一个函数,需要写一个 return 表达式,跟上一个 Lambda、一个成员引用,或者其他的函数类型的表达式,
* 比如一个(函数类型的)局部变量。
*/
// 代码清单8.7 在UI代码中定义一个返回函数的函数
data class Person(
val firstName: String,
val lastName: String,
val phoneNumber: String?
)
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
}
// 从这个函数返回一个lambda
return { startsWithPrefix(it) && it.phoneNumber != null }
}
}
val contacts = listOf(Person("Dmitry", "bin", "188-1"),
Person("Jin", "been", null))
val contactListFilters = ContactListFilters()
// 简化多次使用contactListFilters,apply会返回传入的对象
with(contactListFilters) {
prefix = "Dm"
onlyWithPhoneNumber = true
}
// 将 getPredicate 返回的函数作为参数传递给 filter 函数
println(contacts.filter(contactListFilters.getPredicate()))
/**-------------------- 8.1.6 通过lambda去除重复代码 ----------------------*/
// 代码清单8.8 定义站点访问数据
data class SiteVisit(val path: String, val duration: Double, val os: Os)
// enum class Os { WINDOWS, LINUX, MAC, IOS, ANDROID }
val log = 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)
)
// 代码清单8.9 使用硬解码的过滤器分析站点访问数据
val average = log.filter { it.os == Os.WINDOWS }
.map(SiteVisit::duration)
.average()
println(average)// 23.0
// 代码清单8.10 用一个普通方法去除重复代码
fun List<SiteVisit>.averageDurationFor(os: Os) =
// 将重复代码抽取到函数中
filter { it.os == os }.map(SiteVisit::duration).average()
println(log.averageDurationFor(Os.WINDOWS))// 23.0
println(log.averageDurationFor(Os.MAC))// 22.0
// 代码清单8.11 用一个复杂的硬编码函数分析站点访问数据
val averageMobileDuration = log.filter { it.os in setOf(Os.IOS, Os.ANDROID) }.map(SiteVisit::duration).average()
println(averageMobileDuration)// 12.15
// 代码清单8.12 用一个高阶函数去除重复代码
fun List<SiteVisit>.averageDurationFor2(predicate: (SiteVisit) -> Boolean) =
filter(predicate).map(SiteVisit::duration).average()
println(log.averageDurationFor2 {
it.os in setOf(Os.ANDROID, Os.IOS)
})// 12.15
println(log.averageDurationFor2 {
it.os == Os.IOS && it.path == "/singup"
})
8.2 内联函数:消除lambda带来的运行时开销
/**-------------------- 8.2.1 内联函数如何运作 ----------------------*/
// 代码清单8.13 定义一个内联函数
// 函数用于确保一个共享资源不会并发地被多个线程访问。函数锁住一个Lock对象,执行代码块,然后释放锁。
// inline fun <T> synchronized(lock: Lock, action: () -> T): T {
// lock.lock()
// try {
// return action()
// } finally {
// lock.unlock()
// }
// }
val l = ReentrantLock()
synchronized(l) {}
fun foo(l: Lock) {
println("Before sync")
synchronized(l) {
println("Action")
}
println("After sync")
}
// 编译后的 foo 函数为:
fun _foo() {
println("Beforesync ")
l.lock()
try {
println("Action")
} finally {
l.unlock()
}
println("After sync")
}
// 在调用内联函数的时候也可以传递函数类型的变量作为参数
class LockOwner(val lock: Lock) {
fun runUnderLock(body: () -> Unit) {
synchronized(lock, body)
}
}
// runUnderLock 会被编译成类似于以下函数的字节码
class LockOwner2(val lock: Lock) {
fun _runUnderLock(body: () -> Unit) {
lock.lock()
try {
// body 没有内联,因为在调用的地方还没有lambda
body()
} finally {
lock.unlock()
}
}
}
/**-------------------- 8.2.2 内联函数的限制 ----------------------*/
// 一般来说,参数如果被直接调用或者作为参数传递给另一个 inline 函数,他是可以被内联的。
// fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {
// return TransformingSequence(this, transform)
// }
// 接收非内联lambda的参数,可以用 noinline 修饰符来标记它:
// inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
//
// }
/**-------------------- 8.2.3 内联集合操作 ----------------------*/
// 代码清单8.14 使用lambda过滤一个集合
data class Person(val name: String, val age: Int)
val people = listOf(Person("jingbin", 28), Person("mayun", 44))
println(people.filter { it.age < 30 })// [Person(name=jingbin, age=28)]
// 代码清单8.15 手动过滤一个集合
val result = mutableListOf<Person>()
for (person in result) {
if (person.age < 30) {
result.add(person)
}
}
println(result)// [Person(name=jingbin, age=28)]
/*
* filter被声明成了内联函数,以上两个代码产生的字节码其实是一样的。
*/
println(people.filter { it.age < 30 }.map(Person::name))// jingbin
/**
* filter 和 map 都是内联函数。
* filter 创建了一个中间集合来保存列表过滤的结果,然后map函数生成的代码会读取这个这个结果。
* 如果有大量的元素处理,中间的开销还有问题,这时可以在调用链的后面加上一个 asSequence 调用,
* 用序列来替代集合,大量的数据处理时加上即可。
*/
// 用序列替代集合?
println(people.filter { it.age < 30 }.map(Person::name).asSequence())// jingbin
println(people.asSequence().filter { it.age < 30 }.map(Person::name))// jingbin
/**-------------------- 8.2.4 决定何时将函数声明成内联 ----------------------*/
/*
* 使用 inline 关键字只能提高带有lambda参数的函数的性能,其他的情况需要额外度量和研究。
* Kotlin标准库中的内联函数总是很小的。
*/
/**-------------------- 8.2.5 使用内联lambda管理资源----------------------*/
// val l :Lock = ...
// 在加锁的情况下执行指定的操作
// l.withLock { }
// withLock 函数的定义:
// action: 需要加锁的地方被抽取到一个独立的方法中
fun <T> Lock.withLock(action: () -> T): T {
lock()
try {
return action()
} finally {
unlock()
}
}
// 代码清单8.16 在Java中使用 try-with-resource
// static String readFirstLineFromFile(String path) throws IOException {
// try (BufferedReader bufferedReader = new BufferedReader(new FileReader(path))) {
// return bufferedReader.readLine();
// }
// }
// 代码清单8.17 使用use函数作资源管理。[不会引发任何的性能开销]
fun readFirstLineFromFile(path: String): String {
// 构建 BufferedReader ,调用 use 函数,传递一个 lambda 执行文件操作
BufferedReader(FileReader(path)).use { br ->
// 从函数中返回文件的一行
return br.readLine()
}
}
8.3 高阶函数中的控制流
/**-------------------- 8.3.1 lambda 中的返回语句:从一个封闭的函数返回 ----------------------*/
// 代码清单8.18 在一个普通循环中使用return
data class Person(val name: String, val age: Int)
val people = listOf(Person("Alice", 29), Person("Bob", 31))
fun lookForAlice(people: List<Person>) {
for (person in people) {
if (person.name == "Alice") {
println("Found!")
return
}
}
// 如果 people 中没有 Alice ,这一行就会被打印出来
println("Alice is no found")
}
lookForAlice(people)// Found!
// 代码清单8.19 在传递给 forEach 的lambda中使用return
fun lookForAlice2(people: List<Person>) {
people.forEach {
if (it.name == "Alice") {
// 和 8.18 中一样返回
println("Found!!")
return
}
}
println("Alice is no found")
}
// 只有在以lambda作为参数的函数是内联函数的时候 才能从更外层的函数返回。
/**-------------------- 8.3.2 从lambda返回: 使用标签返回 ----------------------*/
/*
* 也可以在 lambda 表达式中使用局部返回。lambda中的局部返回跟for循环中的break表达式类似。
* 要区分局部返回和非局部返回,要用到标签。
*/
// 代码清单8.20 用一个标签实现局部返回
fun lookForAlice3(people: List<Person>) {
// 给 lambda 表达式加上标签
people.forEach lable@{
// return@lable 引用了这个标签
if (it.name == "Alice") return@lable
}
// 这一行总是会打印出来
println("Alice might be somewhere")
}
lookForAlice3(people)// Alice might be somewhere
/*
* people.forEach lable@{
* if (it.name == "Alice") return@lable
* }
* lable@ lambda标签
* @lable 返回表达式标签
*/
// 代码清单8.21 用函数名作为 return 标签
fun lookForAlice4(people: List<Person>) {
people.forEach {
// return@forEach 从lambda表达式返回
if (it.name == "Alice") return@forEach
}
println("Alice might be somewhere")
}
// 带标签的 this 表达式
// 这个 lambda 的隐式接收者可以通过 this@sb 访问
val apply = StringBuilder().apply sb@{
listOf(1, 2, 3).apply {
// this 指向作用域内最近的隐式接收者。
// 所有隐式接收者都可以被访问,外层的接收者通过显示的标签访问
this@sb.append(this.toString())
}
}
println(apply)// [1,2,3]
/**-------------------- 8.3.3 匿名函数:默认使用局部返回 ----------------------*/
// 代码清单8.22 在匿名函数中使用 return
fun lookForAlice5(people: List<Person>) {
// 使用匿名函数取代 lambda 表达式
people.forEach(fun(person) {
// return 指向最近的函数:一个匿名函数
if (person.name == "Alice") return
println("${person.name} is not Alice")
})
}
println(lookForAlice5(people))
// 代码清单8.23 在filter中使用匿名函数
people.filter(fun(person): Boolean {
return person.age < 30
})
// 代码清单8.24 使用表达式体匿名函数
people.filter(fun(person) = person.age < 30)
// return 从最近的使用 fun 关键字声明的函数返回。
// 8.22 中返回的是 fun(person)
// 以下返回的是 fun lookForAlice6
fun lookForAlice6(people: List<Person>) {
people.forEach {
if (it.name == "Alice") return
}
}
总结
- 函数类型可以让你声明一个持有函数引用的变量、参数或者函数返回值。
- 高阶函数以其他函数作为参数或者返回值。可以用函数类型作为函数参数或者返回值的类型来创建这样的函数。
- 内联函数被编译以后,它的字节码连同传递给它的 lambda 的字节码会被插入到调用函数的代码中,这使得函数调用相比于直接编写相同的代码,不会产生额外的运行时开销。
- 高阶函数促进了一个组件内的不同部分的代码重用,也可以让你构建功能强大的通用库。
- 内联函数可以让你使用非局部返回一一在 lambda 中从包含函数返回的返回表达式。
- 匿名函数给 lambda 表达式提供了另一种可选的语法,用不同的规则来解析 return 表达式。可以在需要编写有多个退出点的代码块的时候使用它们。