Lambda
使用 { } 定義Lamba,調用run運行
run { println(1) }
更常用的為 { 參數 -> 操作 },還可以存儲到變量中,把變量作為普通函數對待
val sum = { x: Int, y: Int -> x + y }
println(sum(1, 2))
maxBy()接收一個Lambda,傳遞如下
class Person(val name: String, val age: Int)
val people = listOf(Person("A", 18), Person("B", 19))println(people.maxBy({ p: Person -> p.age }))
println(people.maxBy() { p: Person -> p.age })println(people.maxBy { p: Person -> p.age }) //只有一個參數,可省略()
println(people.maxBy() { p -> p.age }) //可推導出類型
println(people.maxBy() { it.age }) //只有一個參數且可推導出類型,會自動生成it
Lambda可使用函數參數和局部變量
fun printProblemCounts(response: Collection<String>) {var clientErrors = 0var serverErrors = 0response.forEach {if (it.startsWith("4")) {clientErrors++} else if (it.startsWith("5")) {serverErrors++}}}
成員引用
上面通過Lambda將代碼塊作為參數傳遞給函數,若要傳遞代碼塊已被封裝成函數,則需要傳遞一個調用該函數的Lambda,如下計算虛歲
class Person(val name: String, val age: Int) {fun getNominalAge(): Int {return age + 1}
}val people = listOf(Person("A", 18), Person("B", 19))
println(people.maxBy { p: Person -> p.getNominalAge() })println(people.maxBy(Person::getNominalAge)) //成員引用,可以省略多余的函數調用
上面是系統為fun getNominalAge()自動生成的成員引用,實際定義應該如下,把函數轉換為一個值,從而可以傳遞它
- 把函數age + 1傳遞給getNominalAge,通過Person::getNominalAge引用函數
- 直接通過Person::age引用成員
class Person(val name: String, val age: Int) {val getNominalAge = age + 1}val people = listOf(Person("A", 18), Person("B", 19))println(people.maxBy(Person::getNominalAge))println(people.maxBy(Person::age))
若引用頂層函數,則可以省略類名稱,以::開頭
fun salute() = println("Salute")run(::salute)
集合的函數式API
filter和map
filter遍歷集合并篩選指定Lambda返回true的元素,如下遍歷偶數
val list = listOf(1, 2, 3, 4)
println(list.filter { it % 2 == 0 })data class Person(val name: String, val age: Int)
val people = listOf(Person("A", 29), Person("B", 31))
println(people.filter { it.age > 30 })
map對集合中的每一個元素應用給定的函數并把結果收集到一個新的集合
val list = listOf(1, 2, 3, 4)
println(list.map { it * it })data class Person(val name: String, val age: Int)
val people = listOf(Person("A", 29), Person("B", 31))
println(people.map(Person::name))
Lambda會隱藏底層操作,如尋找最大年齡,第一種方式會執行100遍,應該避免
data class Person(val name: String, val age: Int)
val people = listOf(Person("A", 29), Person("B", 31))
people.filter { it.age == people.maxBy(Person::age)!!.age }val maxAge = people.maxBy(Person::age)!!.age
people.filter { it.age == maxAge }
對于Map,可調用filterKeys/mapKeys、filterValues/mapValues
val numbers = mapOf(0 to "zero", 1 to "one")
println(numbers.mapValues { it.value.toUpperCase() })
all、any、count、find
all判斷集合所有元素是否都滿足條件,any判斷至少存在一個滿足條件的元素
data class Person(val name: String, val age: Int)
val people = listOf(Person("A", 26), Person("B", 27))val max27 = { p: Person -> p.age >= 27 }
println(people.all(max27))
println(people.any(max27))
!all()表示不是所有符合條件,可用any對條件取反來代替,后者更容易理解
val list = listOf(1, 2, 3)
println(!list.all { it == 3 })
println(list.any { it != 3 })
count用于獲取滿足條件元素的個數,其通過跟蹤匹配元素的數量,不關心元素本身,更加高效,若使用size則會創建臨時集合存儲所有滿足條件的元素
data class Person(val name: String, val age: Int)
val people = listOf(Person("A", 26), Person("B", 27))
val max27 = { p: Person -> p.age >= 27 }println(people.count(max27))
println(people.filter(max27).size)
find找到一個滿足條件的元素,若有多個則返回第一個,否則返回null,同義函數firstOrNull
data class Person(val name: String, val age: Int)
val people = listOf(Person("A", 26), Person("B", 27))
val max27 = { p: Person -> p.age >= 27 }println(people.find(max27))
println(people.firstOrNull(max27))
groupby
groupby把列表轉成分組的map
data class Person(val name: String, val age: Int)val people = listOf(Person("A", 26), Person("B", 27), Person("C", 27))
println(people.groupBy { it.age })
如上打印
{
26=[Person(name=A, age=26)],
27=[Person(name=B, age=27), Person(name=C, age=27)]
}
flatMap、flatten
flatMap根據給定的函數對集合中的每個元素做映射,然后將集合合并,如下打印 [A, 1, B, 2, C, 3]
val strings = listOf("A1", "B2", "C3")
println(strings.flatMap { it.toList() })
如統計圖書館書籍的所有作者,使用Set去除重復元素
data class Book(val title: String, val authors: List<String>)
val books = listOf(Book("A", listOf("Tom")),Book("B", listOf("john")),Book("C", listOf("Tom", "john"))
)
println(books.flatMap { it.authors }.toSet())
flatten用于合并集合,如下打印 [A1, B2, C3]
val strings = listOf("A1", "B2", "C3")
println(listOf(strings).flatten())
序列
序列的好處
map / filter 會創建臨時的中間集合,如下就創建了2個
data class Person(val name: String, val age: Int)
val people = listOf(Person("A", 26), Person("B", 27))
println(people.map(Person::age).filter { it > 26 })
而使用序列可以避免創建
println(people.asSequence().map(Person::age).filter { it > 26 }.toList())
惰性操作及性能
序列的中間操作都是惰性的,如下不會有輸出
listOf(1, 2, 3, 4).asSequence().map { println("map($it)"); it * it }.filter { println("filter($it)");it % 2 == 0 }
只有當末端操作時才會被調用,如toList()
listOf(1, 2, 3, 4).asSequence().map { println("map($it)"); it * it }.filter { println("filter($it)");it % 2 == 0 }.toList()
- 序列先處理第一個元素,然后再處理第二個元素,故可能有些元素不會被處理,或輪到它們之前就已經返回
- 若不使用序列,則會先求出map的中間集合,對其調用find
println(listOf(1, 2, 3, 4).asSequence().map { print(" map($it)");it * it }.find { it > 3 })
println(listOf(1, 2, 3, 4).map { print(" map($it)");it * it }.find { it > 3 })
如上都打印4,但序列運行到第二個時已找到滿足條件,后面不會再執行
map(1) map(2)4
map(1) map(2) map(3) map(4)4
序列的順序也會影響性能,第二種方式先filter再map,所執行的變換次數更少
data class Person(val name: String, val age: Int)
val people = listOf(Person("A", 26), Person("AB", 27), Person("ABC", 26), Person("AB", 27))println(people.asSequence().map(Person::name).filter { it.length < 2 }.toList())println(people.asSequence().filter { it.name.length < 2 }.map(Person::name).toList())
創建序列
generateSequence根據前一個元素計算下一個元素
val naturalNumbers = generateSequence(0) { it + 1 }
val numbersTo100 = naturalNumbers.takeWhile { it <= 100 }
println(numbersTo100.sum())
和Java一起使用
函數式接口(SAM接口)
若存在如下Java函數
public class Test {public static void run(int delay, Runnable runnable) {try {Thread.sleep(delay);} catch (InterruptedException e) {e.printStackTrace();}runnable.run();}
}
對于上面接受Runnable的接口,可以傳遞Lambda或創建實例,前者不會創建新的實例,后者每次都會創建
Test.run(1000, Runnable { println("Kotlin") })
Test.run(1000, { println("Kotlin") })
Test.run(1000) { println("Kotlin") } //最優Test.run(1000, object : Runnable {override fun run() {println("Kotlin")}
})
若Lambda捕捉到了變量,每次調用會創建一個新對象,存儲被捕捉變量的值
- 若捕捉了變量,則Lambda會被編譯成一個匿名類,否則編譯成單例
- 若將Lambda傳遞給inline函數,則不會創建任何匿名類
fun handleRun(msg: String) {Test.run(1000) { println(msg) }
}
SAM構造方法
大多數情況下,Lambda到函數式接口實例的轉換都是自動的,但有時候也需要顯示轉換,即使用SAM構造方法,其名稱和函數式接口一樣,接收一個用于函數式接口的Lambda,并返回這個函數式接口的實例
val runnable = Runnable { println("Kotlin") } //SAMrunnable.run()
如下使用SAM構造方法簡化監聽事件
val listener = View.OnClickListener { view ->val text = when (view.id) {1 -> "1"else -> "-1"}println(text)
}
帶接收者的Lambda
with
fun alphabet(): String {val result = StringBuilder()for (letter in 'A'..'Z') {result.append(letter)}result.append("\nover")return result.toString()
}
上面代碼多次重復result這個名稱,使用with可以簡化,內部可用this調用方法或省略
- with接收兩個參數,下面例子參數為StringBuilder和Lambda,但把Lambda放在外面
- with把第一個參數轉換成第二個參數Lambda的接收者
fun alphabet(): String {val result = StringBuilder()return with(result) {for (letter in 'A'..'Z') {this.append(letter)}append("\nover")toString()}
}
還可以進一步優化
fun alphabet() = with(StringBuilder()) {for (letter in 'A'..'Z') {this.append(letter)}append("\nover")toString()
}
apply
with返回的是接收者對象,而不是執行Lambda的結果,而使用apply()會返回接收者對象,可以對任意對象上使用創建對象實例,還能代替Java的Builder
fun alphabet() = StringBuilder().apply {for (letter in 'A'..'Z') {this.append(letter)}append("\nover")
}.toString()
使用庫函數buildString還可以簡化上述操作
fun alphabet() = buildString {for (letter in 'A'..'Z') {this.append(letter)}append("\nover")
}