四、Lambda和集合
1、Lambda简化表达
函数式接口:有且仅有一个抽象方法的接口
with 和 apply 的实现
with、apply是一个函数,其实它有两个参数,第一个参数是需要传入的对象,第二个参数是一个lambda表达式,根据lambda表达式的语法规则,这个lambda表达式可以写在括号外面。wit、apply函数可以让在这个lambda表达式中,所有涉及到对result对象操作的函数都可以省略调用对象result而直接调用其函数。
with 函数:
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
apply 函数:
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
使用示例:
fun alphabet() = with(StringBuffer()){ //重构
for(letter in 'A'..'Z'){
append(letter)
}
append("\nNow I Konw the alphabet")//可以省略this
toString() //从lambda返回值
}
fun alphabet() =StringBuilder().apply{
for(letter in 'A'..'Z'){
append(letter)
}
append("\nNow I Konw the alphabet")//可以省略this
} .toString() //从lambda返回值
with 和 apply 都调用了传入的 block 函数。它们区别在于:
with 有一个 receiver 参数来表示接收者,而 apply 是一个扩展函数,调用者可以直接调用 apply。
with 的返回值是 block 函数的返回值,而 apply 的返回值是调用者本身。
为什么叫做带接收者的lambda,就是无论是我们传递给with还是apply函数的对象,都可以在lambda内部自由调用这个对象的方法,这在Java里面是没有这种能力的,这里我们传递给with或者apply的那个对象就是我们所谓的接收者
2、集合的高阶函数API
1、map、filter
filter主要用作过滤器(对集合元素),它的返回值也是一个集合,定义如下:
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
使用
val filterList = list.filter { it.age > 30 }//把list集合中age属性大于30 的元素放到新的集合里返回来
map主要对集合的元素做映射,可以映射成不同的类型,例如Int到String,定义如下
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}
使用
val mapList = list.map { it.name }//把list集合中所有元素的name属性的值放到新的集合里返回来。
2、all 、any 、count、 find
all检查集合中是否所有元素都符合某个条件
示例 val all = list.all { it.age > 10 } 返回false
any检测集合中是否至少存在一个元素满足条件,如果是就返回true
示例val any = list.any { it.age > 10 } 返回true
count 检查集合中有多少个元素满足该条件,返回值是Int
示例val count = list.count { it.age > 25 } 返回3
find找到集合中第一个满足条件的元素,返回值是集合元素类型,可能为null
示例 val find = list.find { it.age > 25 }
groupBy主要用作将集合按照给定的条件进行分组,即将集合转为map
示例 val groupBy = list.groupBy { it.sex} 返回map 结构{男=[元素],女=[元素]}
3、flatMap、flatten
flatten 就是对几个集合进行扁平化合并得到一个新的集合
public fun <T> Iterable< terable<T>>.flatten(): List<T> {
val result = ArrayList<T>()
for (element in this) {
result.addAll(element)
return result
}
使用
val list = listOf(
1..3,
4..7,
9..11
)
val flatList = list.flatten()
flatMap是先对列表进行map操作再进行flatten操作
public inline fun <T, R> Iterable<T>.flatMap(transform: (T) -> Iterable<R>): List<R> {
return flatMapTo(ArrayList<R>(), transform)
}
使用 :对集合citys里边属性为people 的元素进行合并得到一个新的集合
val flatmap = citys.flatMap {
it.people
}
当我们需要对一个集合进行扁平化操作的时候我们用flatten,当需要对集合中一些元素进行加工处理的时候,我们用flatMap。
4、别样的求和方式:sum sumBy fold reduce
sum对数值类型进行求和,sumBy是对指定元素的某个数值属性进行求和
val list = listOf(1,2,3,4,5)
val total = list.sum()或者val total = list.sumBy(it)
val scoreTotsl = students.sumBy {it.score}
fold reduce
/**
* 常见高阶函数
* fold函数:累加函数,与reduce方法类似,不过可以设置初始值,
* 如果初始值可以是StringBuilder,可用来拼接字符串,
* 参数acc的类型与初始值一致
*/
fun factorial2(n: Int): Int { //求阶乘
if (n == 0) return 1
return (1..n).reduce { acc, i -> acc * i }
}
fun main(args: Array<String>) {
println("打印0到6的阶乘")
(0..6).map(::factorial2).forEach(::println) //分别求0到6的阶乘再遍历打印
val list = listOf(1,2,3,4)
println("打印1到4的和")
println(list.fold(0, {acc, i -> acc + i })) //以0为初始值,求1到4的和
println("打印0到6求得各自阶乘后的和")
//fold方法相比reduce方法类似,不过可以设置初始值
println((0..6).map(::factorial2).fold(5) { acc, i -> acc + i })
println("打印0到6求得各自阶乘后拼接的字符串")
//分别求0到6的阶乘再让阶乘的值之后拼接字符串再打印
println((0..6).map(::factorial2).fold(StringBuilder()) { acc, i ->
acc.append(i).append(",")
})
println("打印0到6逆序求得各自阶乘后拼接的字符串")
//foldRight是fold的逆序操作
println((0..6).map(::factorial2).foldRight(StringBuilder()) { i, acc ->
acc.append(i).append(",")
})
println((1..6).joinToString(",")) //拼接字符串的另一种方法
}
集合的设计
集合分为List(不可变的list,实现了Collection接口)和MutableList(可变的list,实现了MutableCollection和List接口)。
4、内联函数
1、在kotlin中每声明一个lambda表达式,字节码就会产生一个匿名类,该匿名类包含一个调用Lmabda的invoke方法,每调用一次遍生成一个新对象。比较消耗资源,所以我们采用内联函数的思想解决这个问题,在方法前边加inline即可。
尽量避免对具有大量函数体的函数进行内联,这样会导致过多的字节码数量,一旦一个函数被定义为内联函数,便不能获取闭包类的私有成员,除非你把他们声明为internal。
2、一个函数的开头加上inline修饰符,那么他的函数体及Lambda参数都会被内联,如果需要对内联函数的某个参数做非内联只需在参数前加noinline即可。
inline fun foo(block1:()->unit,noinline block2:()->unit){}
3、非局部返回,内联函数的return会直接退出上一层函数,不是退出内联函数本身。除了使用inline内联函数实现非局部返回,还可以用标签@实现。
例如:forEach接口接收的是一个内联函数的Lambda参数
fun hasZeros(list:List<Int>):Boolean{
list.forEach{
if(it==0){
return true //直接返回hasZeros函数结果
}
}
return false
}
4、泛型在运行时会被类型擦除,但是在inline函数中我们可以指定类型不被擦除, 因为inline函数在编译期会将字节码copy到调用它的方法里,所以编译器会知道当前的方法中泛型对应的具体类型是什么,然后把泛型替换为具体类型,从而达到不被擦除的目的,在inline函数中我们可以通过reified关键字来标记这个泛型在编译时替换成具体类型
示例 inline fun <reified T> Int.toCase():T?{
return if (this is T) {
this
} else {
null
}
}
类型擦除的问题
泛型读取时会进行自动类型转换问题,所以如果调用泛型方法的返回类型被擦除则在调用该方法时插入强制类型转换
泛型类型参数不能是基本类型, 擦除后的Object 是引用类型不是基本类型
无法进行具体泛型参数类型的运行时类型检查, instanceof ArrayList<?>
不能抛出也不能捕获泛型类的对象,因为异常是在运行时捕获和抛出的,而在编译时泛型信息会被擦除,擦除后两个 catch 会变成一样的东西。不能在 catch 子句中使用泛型变量,因为泛型信息在编译时已经替换为原始类型(譬如 catch(T) 在限定符情况下会变为原始类型 Throwable),如果可以在 catch 子句中使用,则违背了异常的捕获优先级顺序
关注我获取更多知识或者投稿