实现原理
(1). 一个Lamdba 表达式把一小段行为进行编码,可以把他当成值到处传递
(2). 可以被独立声明并存储到一个变量中
(3). 直接声明传递给函数
使用格式(方法):
(1). 中间的 -> 是用来隔开参数和函数体(前面是参数,后面是函数体 参数和函数体合起来成为 一个实参)
(2) 实参也就是Lambda表达式,Lambda表达式始终在花括号中。
如:
class Lambda{
companion object {
/*
Lambda 表达式全部都必须写到 花括号{} 中,在Lambda表达式中 -> 是参数和函数体的分隔符
将整个Lambda存入 sum 变量中
*/
val sum = { x: Int , y: Int -> x+y}
//lambda没有限制在小的规模,它可以包含更多的语句
val sum = { x: Int , y: Int -> print("Computing the sum of y")
x+y}
}
fun test{
// 可以把这个变量当作普通函数使用
print(sum(1,2))
// 顺便复习一下伴生对象
print(Lambda.sum(1,2))
}
}
(3). Lambda 语法规定,如果Lambda表达式是函数调用的最后一个参数,它可以放到括号的外边。
(4). Lmabda 语法规定,如果Lambda表达式是函数唯一的实参时,可以去掉调用者后面的空括号对。
(5). 不能把超过一个的Lambda放在调用者参数括号的外面,最多只能在括号外放一个Lambda表达式在括号外
it 使用原理:
1. 使用默认参数名称it代替命名参数,如果当前上下文期望只有一个参数的lambda,且这个参数的类型可以推断出来,就会生成这个名称。
如:以下的例子是实现比较年龄的大小而返回输出真个整个对象
//正常的代码实现
data class Person(val name: String,val age: Int){
fun findTheOldest(people: List<Person>){
var maxAge = 0
var theOldest: Person? = null
for (person in people){
if (person.age > maxAge) {
maxAge = person.age
theOldest = person
}
}
print(theOldest)
}
fun test(){
// 封装两个List对象
val people = listOf(Person("Alice",29),Person("Bob",31))
// 将连个list对象传入 findTheOldest方法
findTheOldest(people)
}
}
演示:这是it 和 lambda
//使用lambda实现
data class Person(val name: String,val age: Int){
fun findTheOldest(people: List<Person>){
}
fun test(){
// 封装两个list对象,对象是以 键-值 (key - value)方式存入的
val people = listOf(Person("Alice",29),Person("Bob",31))
// 使用扩展函数 maxBy ,因为上下文只有一个 people对象,所以 it 可以代替
//people引用
print(people.maxBy { it.age })
// 第二种实现方式
print(people.maxBy ({ P: Person -> P.age }))
// 第三种实现方式语法规定:如果Lambda表达式是函数调用的最后一个参数,它可以放
// 到括号的外边
print(people.maxBy(){ p: Person -> p.age })
// 第四种,当lamdba是唯一的实参时,还可以去掉代码中的括号对
print(people.maxBy { p:Person -> p.age })
// 修改两个list对象的分隔符
print(people.joinToString (separator = "",transform = { p: Person -> p.name}))
// 在括号外传递参数
print(people.joinToString(""){ p: Person -> p.name })
}
}
演示:lambda修改局部变量
fun printMessagesWitchPrefix(message: Collection<String>, prefix: String){
var clientErrors = 0
var serverError = 0
// startWitch("4") : 如果该字符串以指定前缀开头,则返回true
/**
* kotlin 可以在lambda中修改非find局部变量和访问
*/
message.forEach{
if (it.startsWith("4")){
clientErrors ++
}else if (it.startsWith("5")){
serverError ++
}
}
}
成员引用 “::”运算符
(1)如果Lambda函数要进行传递可以使用 " :: "两个冒号来进行把函数转换成一个值
liasr
fun salute() = print(Person::age)
fun a(){
/**
* 成员 salute被当作实参传递给库函数 run(),它会调用相应的函数
*/
// run(): 调用指定的函数,并返回其结果,这个地方省略了类名
run(::salute)
}
// 这里是将创建Person实例的动作被保存成了值
val createPerson = ::Person
val p = createPerson("Alice",29)
println(p)
/**
-
"::" 可以以同样的方式引用扩展函数和访问成员实例的成员 Person.isAdult没什么两样
*/
fun Person.isAdult() = age>=21
val predicate = Person::isAdult// kotlin 可以使用已经绑定了的对象(绑定成员引用)
val p = Person("Dmitry",34)
// 这里是使用类名对象来获取引用
val personsAgeFunction = Person::age
// 这里是使用已经被Person引用的引用兑现p来引用age
// 把 p对象中的age成员引用给dmitrysAgeFuntion
val dmitrysAgeFunction = p::age
println(dmitrysAgeFunction)
// 把1,2,3,4 四个元素存入list中,在使用listOf存入对象时,list已经被定义为List
// 类型对象
val list = listOf(1,2,3,4)
// 这里it 默认代替了list对象
/**
- Lambda表达式的参数只有一个的时候可以使用it来使用此参数。it可表示为单个参数的隐式名称
*/
print(list.filter { it % 2 == 0 })
// 把1,2,3,4 四个元素存入list中,在使用listOf存入对象时,list已经被定义为List
val list = listOf(1,2,3,4)
// map把每一个元素应用给定的函数并把结果收集到一个新集合
print(list.map { it * it })
print(list.map { it * it * it })
// 结果 [1, 4, 9, 16] [1, 8, 27. 64]
// 封装两个List对象
val people = listOf(Person("Alice",29),Person("Bob",31))
// 只输出集合中的名字部分
print(people.map { it.name })
// 结果: [Alice, Bob]
// 封装两个List对象
val people = listOf(Person("Alice",29),Person("Bob",31))
// 只需要使用first将大于age大于30的对象然后在花括号后面加上name就可以隐式的调用map
// 将大于30且是name的对象放入新的集合中
people.first{it.age > 30}.name
mapValues 和 filterVlaues 同样是用来过滤 map 和 list中的值
val people = listOf(Person("Alice",29),Person("Bob",31))
// 这里先对查找年龄的最大者
val maxAge = people.maxBy(Person::age)!!.age
// 得到所有年龄最大的人
people.filter { it.age == maxAge }
val numbers = mapOf(0 to "zero", 1 to "one")
print(numbers.mapValues { it.value.toUpperCase() })
// 结果: {0=ZERO, 1=ONE}
"all" "any" "count" 和 "find" : 对集合应用判断式
val people = listOf(Person("Alice",27),Person("Bob",31))
// 检查某一个元素是否小于等于27
val canBeInClub27 = {p: Person -> p.age <= 27}
// 检查全部元素是否都满足小于等于27
print(people.all(canBeInClub27)) // 结果:false
// 检查是否至少有一个元素小于等于27 (any : 任何的)
print(people.any(canBeInClub27)) // 结果:true
// 把1,2,3 三个元素存入list中,在使用listOf存入对象时,list已经被定义为List类型对象
val list = listOf(1,2,3)
// 检查是否所有元素都等于3 ,如果都不等于3则将all函数输出的false取反为true
print(!list.all { it == 3 })
// 检查是否有元素不等于3
print(list.any { it != 3 })
count 和 size
val people = listOf(Person("Alice",27),Person("Bob",31))
// 检查某一个元素是否小于等于27
val canBeInClub27 = {p: Person -> p.age <= 27}
// 检查有多少元素满足条件
print(people.filter(canBeInClub27).size)
// 检查有多少元素满足条件
print(people.count(canBeInClub27))
find 和 firstOrNull
val people = listOf(Person("Alice",27),Person("Bob",31))
// 检查某一个元素是否小于等于27
val canBeInClub27 = {p: Person -> p.age <= 27}
// 返回给定匹配的第一个元素,如果没有找到元素则返回null
// find 和 firstOrNull 的意图是一样的
print(people.find(canBeInClub27)) // 结果:Person("Alice",27)
print(people.firstOrNull(canBeInClub27)) // 结果:Person("Alice",27)
groupBy的每一个分组都是存储在一个列表中的,结果的类型是Map<Int, list<Person>>
val people = listOf(Person("Alice",27),Person("Bob",31), Person("Carol",31))
// 把age相同的分到同一组
print(people.groupBy { it.age })
// 结果: {31=[Person(name=Bob,age=31),Person(name=Carol,age=31)],29=[Person(name=Alice,age=29)]}
val strings = listOf("abc", "def")
// flat:平的
// flatMap首先对"abc" "def" 进行隐射成 [a, b, c] [d, e, f] 再合并为 [a, b, c, d, e, f]
print(strings.flatMap { it.toList() }) // 结果:[a, b, c, d, e, f]
flatMap 会移除集合中所有重复的元素只留下其中一个
val books = listOf(Book("Thursday Next", listOf("Jasper Fforde"))
,Book("Mort", listOf("Terry Pratchett"))
,Book("Good Omens", listOf("Terry Pratchett", "Neil Gaiman")))
// 将books 列表中的 Book类的全部authors对象合并成一个一个列表
print(books.flatMap { it.authors}.toSet()) // authors:作者
// 结果: [Jasper Fforde, Terry Pratchett, Neil Gaiman]
惰性集合操作:序列
data class Person(val name:String, val age: Int)
val people = listOf(Person("Alice",27),Person("Bob",31), Person("Carol",31))
// filter 和 Map都会返回一个列表,也就意味着上面例子中的链式调用会放回两个列表
println(people.map(Person::name).filter{it.startsWith("A")})
// asSequece:把操作变成使用序列,而不是直接使用集合 最后再将序列装换成集合 toList()
/**
- 以下的例子中没有创建任何用于存储元素的中间集合
*/
print(people.asSequence().map(Person::name).filter{it.startsWith("A")}.toList())
// 结果:[Alice]
Kotlin 惰性集合操作的入口就是Sequence接口,只提供了一个方法:interator(迭代器),用来从序列中获取值;调用asSequence来吧任意集合装换成序列,调用toList来反向转换。
为什么要把序列装换为集合:当你只需要迭代序列中的元素,那可以直接使用序列,如果你要使用其他Api方法,比如用下表访问元素,那么你需要把序列装换成列表。
使用:如果集合中有大量的元素,元素中间结果进行重新分配开销巨大,所以惰性亲求值是更好的选择。
执行序列操作:中间和末端操作
序列操作分为两类:中间操作和末端的。
中间操作:中间操作返回的是另一个序列,这个序列知道如何变换原始序列中的元素。
末端操作:末端操作返回的是一个结果,这个结果可能是集合,元素,或者从其他初始集合变换的序列中获取的任意对象。
sequence.map{ ... }.filter { ... }.toList()
map{ ... }.filter { ... } :这里是中间操作
toList() :这里是末端操作
复习:
map:把每一个元素应用到给定的函数并把函数应用后得到的结果收集到一个新的集合(也就是在新的表中映射出所有函数对操作后得到的结果)
filter:(过滤)把条件函数传入filter中,filter会过滤出所有达到条件的元素都收集到新的列表中
/**
- 以下代码并不会输出任何内容,这以为着map和filter变换被延期了
- ,他们只会在获取结果的时候被调用(即末端操作被调用的时候)
/
// filter将会过滤map执行之后的元素
val list = listOf(1,2,3,4).asSequence()
// 在lambda的执行顺序是从左到右
.map { println("map(it)"); it % 2 == 0 }.toList()
/
结果:map:1
filter:1
map:2
filter:4
map:3
filter:9
map:4
filter:16
*/
及早求值和惰性求值
及早求值:也就是使用集合去操作,它的求值方式是只有执行完每一步之后再进行下一步的操作
惰性求值:也就是使用序列去操作,它的求值方式是执行完每一个元素就进行下一步操作,第二步操
作执行完之后再再继续执行第一步操作,直到第二步操作终止或者整个操作完成
val list = listOf(1,2,3,4).asSequence()
.map { it * it }
// find得到一个想要的值就会返回结果 终止操作
.find{ it>3 }
print(list) // 结果:4
总结:当你元素数量多时,使用惰性序列来操作会比使用集合操作要节省很多时间
先应用filter有助于减少执行次数
// 第一段代码和第二段代码虽然执行的结果相同,但是第一段执行了6次,第二段代码只执行了4次
print(people.asSequence().map ( Person::name ).filter { it.length < 3 }.toList())
print(people.asSequence().filter{it.name.length< 3 }.map(Person::name).toList())
generateSequence 函数:括号内是元素初始值,花括号是每次迭代的具体操作步骤,每一次迭代之后的值都会产生一个新的序列对原有的值进行跟新
val naturalNumbes = generateSequence(0) {it + 1}
// naturalNunbes调用 takeWhile ,
// 当naturalNumbes 获取到的结果符合条件时就会不断执行naturaNumbes中的操作,直到条件符合
val numberTo100 = naturalNumbes.takeWhile { it <= 100 }
// 当获取结果“sum”时,所有别推迟的操作都会执行
print(numberTo100.sum())
使用序列的优点:序列允许你找到需要的目录之后立即停止遍历目录
fun File.isInsideHiddenDirectoty() =
// parentFile 返回这个路径的父类抽线路径名,
// any: 如果这个路径存在就返回true,不存在 false
generateSequence (this) {it.parentFile}.any{ it.isHidden}
val file = File("/Users/svtk/.HiddenDit/a.txt")
file.isInsideHiddenDirectoty()
使用java函数式接口
函数式接口(SMA):指的式接口中只有一个抽象方法
如:
/* java代码 */
void postponeComputation(Int delay, Runnable computation );
在kotlin 中可以调用它并把一个lambda作为实参传递给它。编译器会自动把它转换为一个Runnable实例。
Lambda调用时,整个程序只会创建一个 Runnable的实例
隐式Lambda调用
postponeComputation(1000){ print(42) }
显示调用时,每次调用都会创建一个新的实例
显示实现匿名内部类
postponeComputation(1000, object : Runnable{
override fun run() {
println(42)
}
})
每次 postponeCompution调用时用的是一个对象
Runnable实例存储在一个变量中
val runnable = Runnable { println(42)}
fun handleComputation() {
// Runnable 接口得到这个变量时编译器就会自动把它转换为一个Runnable实例
postponeComputation(1000,runnable)
}
如果Lambda从包围它的作用域中捕捉了变量,每次调用就不再可能重用同一个实例
// Lambda 会捕捉 “id” 这个变量(也就是说每次传入的id不同Runnable调用的实例也不同)
fun handleComputation(id : String){
// 每次 handleComputation 调用时都创建一个Runnable的实例
postponeComputation(1000) {println(id)}
}
Lambda:在kolin1.0开始,每个Lambda表达式都会编译成一个匿名类,除非它是一个内联Lambda,编译器会为每一个Lambda生成一个独立的.class,如果Lambda捕捉了变量,每个捕捉的变量会在匿名类中有对应的字段,而且每次对Lambda的调用都会创建一个这个匿名类的新实例。
/* java代码 */
void postponeComputation(Int delay, Runnable computation );
反编译:
class HandleComputation1(id))
}
反编译的结果将 话括号内的 println(id) 在后台自动创建了一个实现了 Runnable接口的类,在类中的run方法执行 println(id)
SAM构造方法:显式的把Lambda转换为函数式接口
class Test{
constructor(){
}
// 只接收一个参数,一个被用作函数式接口单抽象方法体的Lambda
// 并返回实现了这个接口的类的实例
fun createAllDoneRunnable():Runnable{
return Runnable{println("All done!")}
}
fun RunCreate(){
// 得到了函数式接口的实例,调用实现
createAllDoneRunnable().run()
}
}
fun main(args: Array<String>){
val test = Test()
test.RunCreate()
}
All done!
Android使用技巧:SAM还可以把Lambda生成的函数存储在一个变量中
val Listener = OnClickListener {
val text = when( view.id ){
R.id/button1 -> "First button"
R.id/button2 -> "Second button"
else -> "Unknown button"
}
toast(test)
}
button1.setOnClickListener(Listener)
button2.setOnClickListener(Listener)
带接收者的Lambda: "with" 与 “aoply”
(Lambda函数是没有实际的对象的,编译器只把Lambda看成一个代码块,所以在使用时不能直接用this引用Lambda函数,在Lambda使用this时只会调用到包围着Lambda的对象)
"with"函数
fun alphabet() : String{
val result = StringBuilder()
// 循环字母 A 至 Z 存入 latter中
for (letter in 'A'..'Z'){
result.append(letter)
}
result.append("\nNow I kNow the alphabet")
return result.toString()
}
with()函数括号内只要传入你想要省略的对象,在with函数内使用这个对象调用属性时,就不需要写出引用名,可以选择用this或者直接选择不写引用就可以直接调用这个引用内对象的属性
fun alphabet1(): String{
val stringBuilder = StringBuilder()
// with():括号内传入的是想要简化的对象
return with(stringBuilder){
for (letter in 'A'..'Z'){
// 在with中也可以使用this代替with函数括号内传入的对象
this.append(letter)
}
// 当然,函数名也可以完全省略
append("\nNow I know the alphabet!")
this.toString()
}
}
直接传入对象,在调用这个函数的时候就可以直接得到先要的结果
fun alphabet2() = with(StringBuilder()){
for (letter in 'A'..'Z'){
this.append(letter)
}
append("\nNow I Know the alphabet!")
toString()
}
冲突:如果你当作参数传给with的对象一斤有这样的方法,该方法的名称和你正在使用with的类中的方法一样,那么可以给你的this加上标签 ,假设函数 aplhabet是类OuterClass的一个方法,如果你想引用的是定义在外部类中的toString 方法,而不是 StringBuilder,可以使用这种语法:
this@OuterClass.toStrng()
"apply"函数
apply 函数几乎和with 一摸一样,唯一的区别是apply始终会返回实参给它的对象。
fun alphabet2() = StringBuilder().apply{
for (letter in 'A'..'Z'){
this.append(letter)
}
append("\nNow I Know the alphabet!")
}.toString()
apply允许你使用紧凑的表达式函数风格
新的TextView实例创建后立即被传给了 apply。TextView 变成了Lambda的接收者,你就可以调用它并修改它的属性
fun createViewWithCustomAttributes(context: Context) =
TextView(context).apply {
text = "Smaple Text"
textSize = 20.0F
setPadding(10,0,0,0)
}
with 和 apply 的本质上的区别就是创建者风格的不同,apply比with更紧凑
Lambda高阶使用
(1)Lambda函数类型
Lambda 是有返回类型的
如·:
val sum = {x: Int, y: Int -> x+y} // 这里的x和y是省略了类型
val action = {println}
显示声明:
val sum : (Int, Int) -> Int = {x, y -> x+y} 返回值类型为 : Int
val action: () -> Unit = {println(42)} 返回值类型为 :Unit
Unit 类型: 表示函数不返回任何有用的值
变量的返回值可空:
var canReturnNull: (Int, Int) -> Int? = { null }
变量本身可空:
var funOrNull: ((Int ,Int) -> Int)? = null
(2)调用作为参数的函数
// 在参数列表中实现一个 operation 参数,称为函数的类型参数
fun twoAndThree(operation: (Int, Int) -> Int){
// 定义具体的值,在调用时只需要传入类型参数的名称,也就是给参数附上名
val result = operation(2,3)
println("The result is $result")
}
fun testTwo(){
twoAndThree { a, b -> a+b }
}
fun String.filter(predicate: (Char) -> Boolean) : String)
: String: 接收者类型
: predicate: 参数类型
: char: 作为参数传递的函数的参数类型
: Boolean: 作为参数传递的函数的返回类型
:(Char) -> Boolean: 函数类型参数
如: 实现一个简单版本的filter函数
// 传入字符数据值,如果不是Char就返回false,是返回true
fun String.filter(preduicate: (Char) -> Boolean) : String{
val sb = StringBuilder()
// until: 到什么之前,返回到什么之前但不包括值本身的值,0 到 length-1
/**
* 这里调用了String里面的 length字段
* index: 用来存储当前项的位置
* length : 表示String 当前字符串的长度
*
* 也就是 从index 在 0 到 length 之内
*/
for (index in 0 until length) {
val element = get(index)
// element 传入的是字符,preduicate返回 true,
// 执行将当前element中的字符添加进StringBuilder中
if(preduicate(element)) sb.append(element)
}
// 返回当前 StringBuilder 内的字符串
return sb.toString()
}
fun testf(){
println("ab1c".filter { it in 'a'..'z' })
// abc
}
给函数类型参数指定Lambda默认值
fun <T> Collection<T>.JoinToString(
// serparator : 分离器(分隔符)
separator: String = " ,",
// prefix: 前缀
prefix: String = "",
// postfix; 后缀
postfix: String = "",
// 声明一个以Lambda为默认函数的类型
// 把 T 类型转为 String 类型
transform: (T) -> String = { it.toString() }
): String{
// 先把prefix(前缀)写入到StringBuilder字符缓冲流中
val result = StringBuilder(prefix)
// index :项,用来标记当前集合中有多少个集合项
// element: 这个字段存着字符串的内容,完整声明为 val element: T、
/**
* (index, element):当前项中的内容
* withIndex: 使用当前列表对象的项标
*/
for((index, element) in this.withIndex()){
if (index > 0) result.append(separator)
// 调用作为实参传递给 “transform” 新参的参数
// 也就是将 element 转为 String类型,再把字符串增加进StringBuilder内
result.append(transform(element))
}
result.append(postfix)
return result.toString()
}
fun testJoin(){
val letters = listOf("Alpha", "Beta")
// 调用JoinToString 函数
println(letters.JoinToString())
// Alpha, Beta
println(letters.joinToString { it.toLowerCase() })
// alpha, beta
println(letters.joinToString(separator = "! ", postfix = "! ",
transform = {it.toUpperCase()}))
// ALPHA! BETA!
}
使用函数类型的可空参数
invoke: 表示调用当前对象
fun <T> Collection<T>.JoinToString(
// serparator : 分离器(分隔符)
separator: String = " ,",
// prefix: 前缀
prefix: String = "",
// postfix; 后缀
postfix: String = "",
// 声明一个以Lambda为默认函数的类型
/* (T) -> String :传入T类型,但Lambda的返回类型为String ,
也就说transform传入的类型可以是任何类型,但如果使用transform时,
它的类型最终会是String,相当于传入类型任意,真实类型为String.
/
// ((T) -> String)? : 声明一个函数类型的可空参数
transform: ((T) -> String )?= { it.toString() }
): String{
// 先把prefix(前缀)写入到StringBuilder字符缓冲流中
val result = StringBuilder(prefix)
// index :项,用来标记当前集合中有多少个集合项
// element: 这个字段存着字符串的内容,完整声明为 val element: T、
/*
* (index, element):当前项中的内容
* withIndex: 使用当前列表对象的项标
*/
for((index, element) in this.withIndex()){
if (index > 0) result.append(separator)
// invoke: 这是一个接口,它可以调用当前函数的对象
// 复习:"?." : 如果transform为null.就返回null.如果不为空则调用后面的对象
// 复习:"?:" : 如果 ?: 符号前面的结果不为空,则返回前面得到的结果
val str = transform?.invoke(element) ?: element.toString()
// 调用作为实参传递给 “transform” 新参的参数
// 也就是将 element 转为 String类型,再把字符串增加进StringBuilder内
result.append(str)
}
result.append(postfix)
return result.toString()
}
(3)返回函数的函数
程序中的一段逻辑可能因为程序的状态或者其他条件而发生变化
例子一:
// 写一个枚举类,用来选择逻辑方案
// STANDARD : 标准, EXPEDITED : 加快
enum class Delivery {STANDARD, EXPEDITED}
class Order(val itemCount: Int)
// 运输成本计算
/**
- 这个函数返回了一个函数,这个返回的函数以Order作为函数类型的值,
- 并以Double作为返回类型。
- 要返回一个函数需要写上 return 加上一个 Lambda 和一个 成员引用
*/
fun getShippingCostCalculator(delivery: Delivery)
// 声明一个返回函数的函数
: (Order) -> Double{
if (delivery == Delivery.EXPEDITED) {
// 隐式声明 order: Order
return {order -> 6+2.1 * order.itemCount}
}
return {order -> 1.2 * order.itemCount}
}
fun testCalculator(){
// 先传入选择参数,再使用函数引用传入返回的Lambda函数的参数
val calculator = getShippingCostCalculator(Delivery.EXPEDITED)
println("Shipping costs ${calculator(Order(3))}")
// Shipping costs 12.3
}
例子二:
data class PersonCL(
val firstName:String,
val lastName: String,
val phoneNumber: String?
)
class CountactListFilters{
var prefix: String = ""
var onlyWithPhoneNumber: Boolean = false
// 声明一个返回函数的函数
fun getPredicate(): (PersonCL) -> Boolean{
// 声明一个函数类型的变量
val startsWithPrefix = { p: PersonCL ->
// 第一个元素中有prefixzi字符 或者最后一个元素有字符 prefix
p.firstName.startsWith(prefix) || p.lastName.startsWith(prefix)
}
if (!onlyWithPhoneNumber){
// 返回一个函数类型的变量
return startsWithPrefix
}
// 从这个函数返回一个lambda
return { startsWithPrefix(it) && it.phoneNumber != null}
}
}
fun testCount(){
val contacts = listOf(PersonCL("Dmitry", "Jemerov", "123-4567"),
PersonCL("Svetlana", "Isakova", null))
// 实例化类
val contactListFilters = CountactListFilters()
// 使用with 在with块内可以省略写括号内的引用
// ,可以直接用this或者不用任何写法就可以调用括号内对象的任意属性
with(contactListFilters){
prefix = "Dm"
onlyWithPhoneNumber = true
}
// 使用filter筛选,通过调用contactListFilters.getPredicate() 得到条件函数
println(contacts.filter(contactListFilters.getPredicate()))
// [PersonCL(firstName=Dmitry, lastName=Jemerov, PhoneNumber=123-4567)]
}
(4)通过Lambda去除重复代码
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("/", 12.0, OS.MAC),
SiteVisit("/login", 12.0, OS.WINDOWS),
SiteVisit("/signup", 8.0, OS.IOS),
SiteVisit("/", 16.3, OS.ANDROID)
)
val averageWindowDuration = log
.filter { it.os == OS.WINDOWS }
// 这里使用了 :: 运算符,SiteVisit::duration 与 SiteVisit.duration 意义相同
.map(SiteVisit::duration)
.average()
// println(averageWindowDuration) 结果: 23.0
// 使用一个普通方法去除重复代码
fun List<SiteVisit>.averageDurationFor(os: OS) =
// average :返回集合中的平均数
// filter:如果条件满足就会返回满足条件的对象
filter { it.os == os }.map(SiteVisit::duration).average()
/*
println(log.averageDurationFor(OS.WINDOWS))
23.0
println(log.averageDurationFor(OS.MAC))
22.0
*/
一个复杂的编码函数复习站点访问数据
val averageMoblieDuration = log
.filter { it.os in setOf(OS.IOS, OS.ANDROID) }
.map (SiteVisit::duration )
.average()
/*
println(averageMobileDuration)
12.15
*/
用一个高阶函数去除重复代码
fun List<SiteVisit>.averageDurationFor(predicate: (SiteVisit) -> Boolean) =
filter(predicate).map(SiteVisit::duration).average()
/*
println(log.averageDurationFor{ it.os in setOf(OS.ANDROID, OS.IOS)})
12.5
println(log.averageDurationFor{ it.os == OS.IOS && it.path == "/singnup"})
8,0
*/
(5)内联函数:消除Lambda带来的开销
inline : 当一个函数被声明成inline时,那么这个函数是内联的,还句话说,函数体会被直接替换到函数被掉用的地方,而不是被正常调用。
inline的使用是有所限制的:
inline只适合涵数体内代码简单的函数数使用,不能包含复杂的结构控制语句例如while、switch,并且内联函数本身不能是直接递归函数(自己内部还调用自己的函数)。
慎用内联:
内联能提高函数的执行效率,为什么不把所有的函数都定义成内联函数?如果所有的函数都是内联函数,还用得着“内联”这个关键字吗?
内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收
获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
以下情况不宜使用内联:
(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。
一个好的编译器将会根据函数的定义体,自动地取消不值得的内联(这进一步说明了inline 不应该出现在函数的声明中)。
使用:
使用 inline 标记函数,编译器会把每一次函数调用都转换成函数实际的代码实现。如果函数使用了lambda实参,lambda也会内联,所以不会创建任何匿名类。
网友评论