前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >让你迷惑的 Kotlin 代码(4)

让你迷惑的 Kotlin 代码(4)

作者头像
路遥TM
发布2021-08-31 15:43:26
3300
发布2021-08-31 15:43:26
举报
文章被收录于专栏:路遥的专栏路遥的专栏

上一期 有人留言说 “就没回答对过” 。甭担心,晒一下前几期的正确率,你并不孤单 ~

期数

投票人数

正确人数

正确率

1

/

/

/

2

93

33

35%

3

139

26

19%

前面连续说了两期 Lambda,今天来一道集合相关的题目。上菜!

代码语言:javascript
复制
fun main(args: Array<String>) {
    listOf(1, 2, 3).filter {
        print("$it ")
        it >= 2
    }
    print("- ")
    listOf(1, 2, 3)
        .asSequence()
        .map {
            print("$it")
            it + 1
        }
        .filter {
            print("$it ")
            it >= 3
        }
}

老规矩,先答题。

答案看起来比较绕,但题目还是比较简单的。

map 函数会逐一变换集合中的元素,其中的打印语句肯定会对所有元素都执行。

可能比较让人纠结的点是,filter 函数中的打印语句,是会对所有元素都执行?还是仅对满足过滤条件的元素执行?

对所有元素都执行,就会打印 1 2 3 - 1 2 3 2 3 4 ,选 C

仅对满足过滤条件的元素执行,就会打印 2 3 - 1 2 3 3 4,选 D

...

...

...

经过这一番理性分析(瞎胡扯),正确答案其实是 以上均不对

print("- ") 为分割线,上面的代码是对 集合 List 进行操作,下面的代码是对集合 List 调用 asSequence() ,转换为 序列 Sequence 再进行操作。这是两段代码的本质区别。

List.filter

先看上半部分代码。不知道前面的瞎胡扯有没有把你说晕,操作符 List.filter() 的逻辑是很简单的。直接看源码。

代码语言:javascript
复制
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate) // 注意看参数,新建了一个 ArrayList
}

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 函数的目的是过滤原集合,输出一个新集合。实现方式就是新建一个新集合,再遍历原集合,逐个元素按过滤条件 predicate 判断,符合条件的就加入新集合。

所以前半部分代码中,集合中的每个元素都会被打印,输出 1 2 3

这里要注意一点,filter 操作符会产生一个新的中间集合。其实不止 filter,所有集合操作符都会产生中间集合。如果有嵌套操作,例如 list.map{ }.filter{ },每一步操作都会产生新的中间集合。

所以当集合元素数量巨大的情况下,会存在一定的性能问题。而下半部分代码中的 Sequence 正好解决了这个问题。

懒惰的 Sequence

回顾一下下半部分的代码。

代码语言:javascript
复制
    listOf(1, 2, 3)
        .asSequence()
        .map {
            print("$it")
            it + 1
        }
        .filter {
            print("$it ")
            it >= 3
        }

没用过 Sequence 的话,也看不出什么花头来,就直接源码搂起来。

先看 asSequence()

代码语言:javascript
复制
public fun <T> Iterable<T>.asSequence(): Sequence<T> {
    return Sequence { this.iterator() } // 注意这个 Sequence 是函数
}

Iterable 对象转化为 Sequence 对象。但要注意第二行中 return 后面跟的 Sequence 其实是个返回值是 Sequence 对象的函数,代码如下:

代码语言:javascript
复制
public inline fun <T> Sequence(crossinline iterator: () -> Iterator<T>): Sequence<T> = object : Sequence<T> {
    override fun iterator(): Iterator<T> = iterator()
}

Sequence() 函数的参数是迭代器 iterator,返回值是 Sequence 对象。Sequence 对象持有了参数中的迭代器。

代码语言:javascript
复制
public interface Sequence<out T> {
    public operator fun iterator(): Iterator<T>
}

所以,集合 List 转换为序列 Sequence ,就等同于序列 Sequence 持有了集合 List 的迭代器。

接着来看序列 Sequence.map 函数。

代码语言:javascript
复制
public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {
    return TransformingSequence(this, transform)
}

Sequence.map() 返回了序列的一个实现类 TransformingSequence ,map 函数的函数类型参数 transform 也一并传给了这个 TransformingSequence

代码语言:javascript
复制
internal class TransformingSequence<T, R>
constructor(private val sequence: Sequence<T>, private val transformer: (T) -> R) : Sequence<R> {
    override fun iterator(): Iterator<R> = object : Iterator<R> {
        val iterator = sequence.iterator() // 获取上一级的迭代器
        override fun next(): R {
            return transformer(iterator.next()) // 重写 next() 方法
        }

        override fun hasNext(): Boolean {
            return iterator.hasNext()
        }
    }

    internal fun <E> flatten(iterator: (R) -> Iterator<E>): Sequence<E> {
        return FlatteningSequence<T, R, E>(sequence, transformer, iterator)
    }
}

TransformingSequence 持有着上一级 Sequence 的迭代器,同时重写了 Sequenceiterator() 方法,定义了自己的迭代器。每取一个元素的时候,通过上一级迭代器的 next() 方法,再执行自己的 transformer() 变换操作。

所以......

Sequence.map() 仅仅只是构建了一个新的 Sequence 。所以,题目中的打印语句自然不会执行。

Sequence.filter() 也是一样,它构建的是一个 FilteringSequence 。这里就不看它的源码了。

前半部分代码会打印 1 2 3 ,后半部分代码什么也不会打印,所以最后的答案是 1 2 3 -

让 Sequence 跑起来

Sequence 是惰性的,它的一系列操作符仅仅只是构建了一个个新的 Sequence 。那么,如果让各个操作符跑起来呢?答案就是,再转换回集合 List

这个操作通过 Sequence.toList() 函数完成,其最终会调用到 Sequence.toCollection()

代码语言:javascript
复制
public fun <T, C : MutableCollection<in T>> Sequence<T>.toCollection(destination: C): C {
    for (item in this) {
        destination.add(item)
    }
    return destination
}

通过 Sequence 的迭代器取出所有元素,加入到集合中来。整个数据流就跑动起来了。

那么,又到了竞猜环节...

代码语言:javascript
复制
fun main() {
    listOf(1, 3, 5)
        .asSequence()
        .map {
            print("$it")
            it + 1
        }
        .filter {
            print("$it ")
            it >= 3
        }.toList()
}

输出结果是 1 2 3 4 5 6 ? 还是 1 3 5 2 4 6

也就是说是逐个元素依次先 map 再 filter ?伪代码如下:

代码语言:javascript
复制
for (item in this) {
    map(item)
    filter(item)
}

还是整个集合先整体 map 再整体 filter ?伪代码如下:

代码语言:javascript
复制
for (item in this) {
    map(item)
}
for (item in this) {
    filter(item)
}

答案是前者。其实前面的 TransformingSequence() 的源码已经告诉了我们答案。

代码语言:javascript
复制
internal class TransformingSequence<T, R>
constructor(private val sequence: Sequence<T>, private val transformer: (T) -> R) : Sequence<R> {
    override fun iterator(): Iterator<R> = object : Iterator<R> { // 定义自己的迭代器
        val iterator = sequence.iterator() // 获取上一级 Sequence 的迭代器
        override fun next(): R {
            return transformer(iterator.next()) // 在自身的 next() 方法中调用上一个 Sequence 的迭代器的 next() 方法
        }

        override fun hasNext(): Boolean {
            return iterator.hasNext() // 调用上一级 Sequence 的迭代器的 hasNext() 方法
        }
    }
 ...
}

在序列 Sequence 的链式操作符调用中,每一个操作符都会生成新的 Sequence,每一个新的 Sequence 又会持有上一级 Sequence ,并在调用自身的迭代器的 next() 和 hasNext() 方法时,调用上一级的 next() 和 hasNext() 方法。

当调用 toList() 操作,使用链式操作末端的操作符产生的 Sequence 的迭代器进行迭代时,会递归调用到最上层的第一个 Sequence 的迭代器,而在调用每一个上级迭代器时,同时又会执行自身操作符的对应操作,比如变换,过滤等。

所以,题目的答案是逐个对集合中的元素先 map 再 filter,输出 1 2 3 4 5 6

另外,如果是先 filter 再 map 的情况,相比先 map 再 filter 可以减少部分不需要的操作。因为不满足 filter 情况的元素就不会再执行 map 了。

Java 也可以?

对于安卓开发来说,可能对 Java8 的 Stream 比较陌生,因为版本限制导致我们不大可能使用 Java8 新特性。

Java8 的 Stream 提供了类似 Kotlin 的处理集合的能力,mapfilterskipflatmap 等等。

那么,它是如 Kotlin 集合一般勤快,还是如 Sequence 一般懒惰呢?

代码语言:javascript
复制
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(3);
        list.add(5);
        list.stream().map(i -> {
            System.out.print(i);
            return i + 1;
        }).filter(i -> {
            System.out.print(i);
            return i >= 3;
        });
    }

上面这段代码会输出什么呢?欢迎在评论区留下你的答案。

往期推荐

让你迷惑的 Kotlin 代码(1)

让你迷惑的 Kotlin 代码(2)

让你迷惑的 Kotlin 代码(3)

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-06-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 路遥TM 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • List.filter
  • 懒惰的 Sequence
  • 让 Sequence 跑起来
  • Java 也可以?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档