Lambda和集合总结( 部分)

在集合操作中使用Lambda会使代码变得非常简洁和优雅,但是这种简洁和优雅也是有代价的,就是在Kotlin中使用Lambda表达式会带来一些额外的开销。为了解决这个问题,所以要了解下内联函数。

1.调用Java函数式接口

textClick.setOnClickListener(object : View.OnClickListener{
    override fun onClick(v: View?) {
        ......
    }
})

 Kotlin允许对Java的类库做一些优化,任何函数接收了一个Java的SAM(单一抽象方法)都可以用Kotlin的函数进行替代。以上的例子可以看成在Kotlin定义了以下方法:

fun setOnClickListener(listener: (View) -> Unit)

listener是一个函数类型的参数,它接收一个类型View的参数,然后返回Unit。所以可以用Lambda语法来简化它:并且listener是setOnClickListener唯一的参数,所以可以省略掉括号。

textClick.setOnClickListener { }

 2.带接收者的Lambda

在Kotlin中,可以定义带有接收者的函数类型:

val sum: Int.(Int) -> Int = { other -> plus(other) }

fun main() {
    println(2.sum(1))
}

3

 上面的代码中,用一个Int类型的变量调用sum方法,传入一个Int类型的参数,对其进行plus操作。

Kotlin有一种神奇的语法——类型安全构造器。用它可以构造类型安全的HTML代码,带接收者的Lambda语法可以很好地应用到其中。

class HTML {
    fun body() {
        //......
        println("construct html")
    }
}

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()// 创建接收者对象
    html.init() //把接收者对象传递给Lambda
    return html
}

fun main() {
    html {
        body()
    }
}


construct html

3.with和apply

这两个方法的最大作用是写Lambda的时候,可以省略需要多次书写的Lambda对象名,默认用this关键字来指向它。

在用Android开发时,进程会给一些视图控件绑定属性。利用with让代码可读性变得更好。

private fun bindData(cacheBean: ContentBean) {
    val title = findViewById<TextView>(R.id.tv_titile)
    val content = findViewById<TextView>(R.id.tv_content)

    with(cacheBean) {
        title.text = this.title
        title.textSize = this.titleFontSize ?: 14.0F
        content.text = payContent //this可以省略
        content.textSize = contentFontSize ?: 12.0F
    }
}

 如果不使用with,就需要写多遍cacheBean。

with在Kotlin库中的定义:

/**
 * Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

 

with函数的第一个参数为接收者类型,然后通过第2个参数创建这个类型的block方法。因此在该接收者对象调用 block方法时,可以在Lambda中直接使用this来代表这个对象。

apply函数

与with函数不同,apply直接被声明为类型T的一个扩展方法,它的block参数是一个返回Unit类型的函数,作为对比,with的block则可以返回自由的类型,两者在很多情况下是可以互相替代的。

/**
 * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
 */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}
private fun bindDataByapply(cacheBean: ContentBean) {
    val title = findViewById<TextView>(R.id.tv_titile)
    val content = findViewById<TextView>(R.id.tv_content)

    cacheBean.apply {
        title.text = this.title
        title.textSize = this.titleFontSize ?: 14.0F
        content.text = payContent
        content.textSize = contentFontSize ?: 12.0F
    }
}

4.集合的高阶函数API

4.1map

在使用集合的时候,会遇到遍历整个集合的情况,在java中通常使用for语句,在Java8之前这样操作是比较繁琐的。用Kotlin实现对列表中元素都乘以2。

fun main() {
    val list = listOf(1, 2, 3, 4)
    val newList = list.map {
        it * 2
    }
    newList.forEach {
        print(it)
    }
}
2468

 通过map方法对集合进行遍历,在遍历过程中,给集合中的每个元素乘以2,最后得到一个新的集合。上面的map方法实际上就是一个高阶函数,它接收的参数实际上就是一个函数。上面的表达式可以修改成下面的样式:

val newList2 = list.map { e -> e * 2 }

 可以看到,map后面的Lambda表达式其实就是一个带有一个参数的匿名函数。

fun payFoo(bar: Int) = bar * 2
val newList3 = list.map { payFoo(it) }

上面的代码其实就是map方法接收了一个函数,这个函数对集合中的每个元素进行操作,然后将操作结果返回,最后产生一个由这些结果组成的新集合。

map源码:

/**
 * Returns a list containing the results of applying the given [transform] function
 * to each element in the original collection.
 */
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}
/**
 * Applies the given [transform] function to each element of the original collection
 * and appends the results to the given [destination].
 */
public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapTo(destination: C, transform: (T) -> R): C {
    for (item in this)
        destination.add(transform(item))
    return destination
}

 在上面的代码中,首先定义了map扩展方法,它的实现主要是依赖mapTo方法。mapTo方法接收两个参数,第一个参数类型是集合(MutableCollection)。第2个参数为一个方法(transform: (T) -> R),最终返回一个集合。在map方法的内部实现其实很简单,就是将transform方法产生的结果添加到一个新集合里面去,最终赶回这个新的集合。

5.对集合进行刷选:filter、count

filter

data class PayStudent(val name: String, val age: Int, val sex: String, val score: Int)

fun main() {
    val stu1 = PayStudent("David1", 30, "m", 85)
    val stu2 = PayStudent("David2", 18, "m", 90)
    val stu3 = PayStudent("David3", 40, "f", 59)
    val stu4 = PayStudent("David4", 30, "m", 70)
    val stu5 = PayStudent("David5", 25, "f", 88)
    val stu6 = PayStudent("David6", 36, "f", 55)


    val stus = listOf(stu1, stu2, stu3, stu4, stu5, stu6)

    val number = stus.filter {
        it.sex == "m"
    }
    println(number)
}

 通过使filter方法,我们就甩选出性别为男的学生。这个方法与map类似,接收一个函数,只是该函数的返回值类型必须是Boolean。该函数的作用就是判断集合中的每一项是否满足某个条件,如果满足,filter方法就会将该项插入新的列表中,最终就得到了一个满足给定条件的新列表。

/**
 * Returns a list containing only elements matching the given [predicate].
 */
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}
/**
 * Appends all elements matching the given [predicate] to the given [destination].
 */
public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
    for (element in this) if (predicate(element)) destination.add(element)
    return destination
}

 可以看到,filter方法的实现主要是依赖filterTo,filterTo接收两个参数,第1个参数destination为一个列表(该方法最终要返回的列表,初始时为空的列表)。第2个参数predicate: (T) -> Boolean是一个返回值类型为Boolean的函数,该函数就是我们在使用filter的时候传入的Lambda表达式。就是通过遍历给定的集合,将每个元素传入predicate函数中,如果返回值为true就保留,反之则丢弃,最终返回满足条件的集合。

列举其他过滤功能的方法:

filterNot与filter的作用相反。

val fStudent = stus.filterNot { it.sex == "m" }
println(fStudent)

filterNotNull 过滤掉值为null的元素。

val notNullStus = stus.filterNotNull()
println(notNullStus)

 count统计满足条件的个数。

val countMStudent = stus.count { it.sex == "m" }
val countFStudent = stus.count { it.sex == "f" }
println(countMStudent)
println(countFStudent)

统计个数可以换一种方式,但是通过先使用filter得到一个满足条件的新列表,然后再统计该新列表的数量,这样就增加了额外的开销。

val countMStudent2 = stus.filter { it.sex == "m" }.size
val countFStudent2 = stus.filter { it.sex == "f" }.size

6.求和方式

例子:求出所有学生的总分。

调用Kotlin集合中的sumBy方法实现求和。

var totalScore = stus.sumBy {
    it.score
}
println(totalScore)

6.1 sum:对数值类型的列表进行求和

sum只能对数值类型的列表进行求和。当然使用sumBy也行。

val a = listOf(1, 2, 3)
val b = listOf(1.1, 2.2, 3.3)
val totalA = a.sum()
val totalB = b.sum()
println(totalA)
println(totalB)

val totalA = a.sumBy { it }
val totalB = b.sumByDouble { it }

6.2 fold

/**
 * Accumulates value starting with [initial] value and applying [operation] from left to right to current accumulator value and each element.
 */
public inline fun <T, R> Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R): R {
    var accumulator = initial
    for (element in this) accumulator = operation(accumulator, element)
    return accumulator
}

 可以看到,fold方法需要接收两个参数,第1个参数initial通常称为初始值,第2个参数operation是一个函数。在实现的时候,通过for语句来遍历集合中方的每个元素,每次都会调用operation函数,该函数的参数与两个,一个是上一次调用该函数的结果(如果是第一次调用,则传入初始化initial值),另外一个则是当前遍历的集合元素。也就是:每次调用operation函数,然后产生的结果作为参数供下一次调用。

例子:通过fold计算出学生的总分。(本质就是一个累加的操作)

val fold = stus.fold(0) { accumulator, student -> accumulator + student.score }
println(fold)

实现一个类乘的操作

val a = listOf(1, 2, 3)
val fold = a.fold(1) { mul, item -> mul * item }
println(fold)

上面的fold很好地利用了递归的思想。

6.3 reduce

reduce与fold非常相似,只是reduce没有初始值。

/**
 * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element.
 */
public inline fun <S, T : S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S {
    val iterator = this.iterator()
    if (!iterator.hasNext()) throw UnsupportedOperationException("Empty collection can't be reduced.")
    var accumulator: S = iterator.next()
    while (iterator.hasNext()) {
        accumulator = operation(accumulator, iterator.next())
    }
    return accumulator
}

 可以发现,reduce方法只接收一个参数,该参数为一个函数。具体的实现方式也与fold类似,不同的是当要遍历的集合为空时,会抛出一个异常。因为没有初始值,所以默认的初始值时集合的第一个元素,所以也可以采用reduce实现求和操作。

val a = listOf(
    1, 2, 3
)
val reduce = a.reduce { acc, i -> acc + i }
println(reduce)
6

6.4通过groupBy进行分组

Kotlin提供了groupBy方法进行分组。

val groupBy = stus.groupBy { it.sex }
println(groupBy)

{m=[PayStudent(name=David1, age=30, sex=m, score=85), PayStudent(name=David2, age=18, sex=m, score=90), PayStudent(name=David4, age=30, sex=m, score=70)], f=[PayStudent(name=David3, age=40, sex=f, score=59), PayStudent(name=David5, age=25, sex=f, score=88), PayStudent(name=David6, age=36, sex=f, score=55)]}

6.5扁平化处理——处理嵌套集合:flatMap、flatten

有时,我们遇到的集合元素不仅仅是数值、类、字符串这种类型,也可能是集合。

flatten将嵌套集合处理成一个集合。

val listOf = listOf(listOf("a", "b", "c"), listOf("d", "e", "f"), listOf("g"))
val flatten = listOf.flatten()
println(flatten)

[a, b, c, d, e, f, g]

 flatten实现原理:首先声明一个集合,然后通过addAll方法将子集合中元素加入到这个新集合中,然后返回。

/**
 * Returns a single list of all elements from all collections in the given collection.
 * @sample samples.collections.Iterables.Operations.flattenIterable
 */
public fun <T> Iterable<Iterable<T>>.flatten(): List<T> {
    val result = ArrayList<T>()
    for (element in this) {
        result.addAll(element)
    }
    return result
}

flatMap接收了一个函数,该函数的返回值是一个列表。下面返回一个由学生组成的列表。

val listOf = listOf(listOf(stu1, stu2, stu3), listOf(stu4, stu5), listOf(stu6))
val flatMap = listOf.flatMap { it.map { it.name } }
println(flatMap)

[David1, David2, David3, David4, David5, David6]

也可以尝试 flatten与map结合使用:

val listOf = listOf(listOf(stu1, stu2, stu3), listOf(stu4, stu5), listOf(stu6))
 val map = listOf.flatten().map { it.name }
 println(map)
 [David1, David2, David3, David4, David5, David6]

 观察另一个例子

data class PayStudent(
    val name: String,
    val age: Int,
    val sex: String,
    val score: Int,
    val hobbies: List<String>
)

fun main() {
    val stu1 = PayStudent("David1", 30, "m", 85, listOf("reading", "coding"))
    val stu2 = PayStudent("David2", 18, "m", 90, listOf("reading", "fishing"))
    val stu3 = PayStudent("David3", 40, "f", 59, listOf("running", "game"))
    val stu4 = PayStudent("David4", 30, "m", 70, listOf("drawing"))
    val stu5 = PayStudent("David5", 25, "f", 88, listOf("writing"))
    val stu6 = PayStudent("David6", 36, "f", 55, listOf("dancing"))
    
    val stus = listOf(stu1, stu2, stu3, stu4, stu5, stu6)
    val flatMap = stus.flatMap { it.hobbies }
    println(flatMap)
}

[reading, coding, reading, fishing, running, game, drawing, writing, dancing]

flatMap是先将列表进行map操作,然后再进行flatten操作的。

val map = stus.map { it.hobbies }
println(map)
[[reading, coding], [reading, fishing], [running, game], [drawing], [writing], [dancing]]
val flatten = map.flatten()
println(flatten)
[reading, coding, reading, fishing, running, game, drawing, writing, dancing]

 观察flatMap的源码:

 

flatMap接收一个函数:transform: (T) -> Iterable<R>

transform函数接收一个参数(该参数一般为嵌套列表中的某个子列表),返回值为一个列表。

flatMap中调用一个flatMapTo的方法。

flatMapTo接收两个参数,一个参数为一个列表,该列表为空列表,另一个是参数为函数,该函数返回值是一个序列。其原理是:首先遍历集合中的元素,然后讲话每个元素传入函数transform中得到一个列表,然后将这个列表中的所有元素添加到空列表destination中,这样就得到了一个经过transform函数处理过的扁平化列表。

/**
 * Returns a single list of all elements yielded from results of [transform] function being invoked on each element of original collection.
 */
public inline fun <T, R> Iterable<T>.flatMap(transform: (T) -> Iterable<R>): List<R> {
    return flatMapTo(ArrayList<R>(), transform)
}


/**
 * Appends all elements yielded from results of [transform] function being invoked on each element of original collection, to the given [destination].
 */
public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.flatMapTo(destination: C, transform: (T) -> Iterable<R>): C {
    for (element in this) {
        val list = transform(element)
        destination.addAll(list)
    }
    return destination
}

 

参考Kotlin核心编程 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值