Kotlin Collection VS Kotlin Sequence VS Java Stream

简介: Kotlin Collection VS Kotlin Sequence VS Java Stream

一. 集合中的函数式 API



虽然 Kotlin Collection 中的函数式 API 类似于 Java 8 Stream 中的 API。但是 Kotlin 的集合跟 Java 的集合并不一致。


Kolin 的集合分为可变集合(mutable collection)和不可变集合(immutable collection)。不可变集合是 List、Set、Map,它们是只读类型,不能对集合进行修改。可变集合是 MutableList、MutableSet、MutableMap,它们是支持读写的类型,能够对集合进行修改的操作。


Kotlin 集合中的函数式 API 跟大部分支持 Lambda 语言的函数式 API 都类似。下面仅以 filter、map、flatMap 三个函数为例,演示使用集合的高阶函数。


1.1 filter 的使用


过滤集合中大于10的数字,并把它打印出来。

listOf(5, 12, 8, 33)   // 创建list集合
            .filter { it > 10 }
            .forEach(::println)


执行结果:

12
33


::println 是方法引用(Method Reference),它是简化的 Lambda 表达式。


上述代码等价于下面的代码:

listOf(5, 12, 8, 33)
            .filter { it > 10 }
            .forEach{ println(it) }


1.2 map 的使用


将集合中的字符串都转换成大写,并打印出来。

listOf("java","kotlin","scala","groovy")
            .map { it.toUpperCase() }
            .forEach(::println)


执行结果:

JAVA
KOTLIN
SCALA
GROOVY


1.3 flatMap 的使用


遍历所有的元素,为每一个创建一个集合,最后把所有的集合放在一个集合中。

val newList = listOf(5, 12, 8, 33)
            .flatMap {
                listOf(it, it + 1)
            }
    println(newList)


执行结果:

[5, 6, 12, 13, 8, 9, 33, 34]


二. Sequence



序列(Sequence)是 Kotlin 标准库提供的另一种容器类型。序列与集合有相同的函数 API,却采用不同的实现方式。


其实,Kotlin 的 Sequence 更类似于 Java 8 的 Stream,二者都是延迟执行。Kotlin 的集合转换成 Sequence 只需使用asSequence()方法。

listOf(5, 12, 8, 33)
            .asSequence()
            .filter { it > 10 }
            .forEach(::println)


亦或者使用sequenceOf()来直接创建新的 Sequence:

sequenceOf(5, 12, 8, 33) // 创建sequence
            .filter { it>10 }
            .forEach (::println)


在 Kotlin 1.2.70 的 release note 上曾说明:


使用 Sequence 有助于避免不必要的临时分配开销,并且可以显着提高复杂处理 PipeLines 的性能。


下面编写一个例子来证实这个说法:

@BenchmarkMode(Mode.Throughput) // 基准测试的模式,采用整体吞吐量的模式
@Warmup(iterations = 3) // 预热次数
@Measurement(iterations = 10, time = 5, timeUnit = TimeUnit.SECONDS) // 测试参数,iterations = 10 表示进行10轮测试
@Threads(8) // 每个进程中的测试线程数
@Fork(2)  // 进行 fork 的次数,表示 JMH 会 fork 出两个进程来进行测试
@OutputTimeUnit(TimeUnit.MILLISECONDS) // 基准测试结果的时间类型
open class SequenceBenchmark {
    @Benchmark
    fun testSequence():Int {
        return sequenceOf(1,2,3,4,5,6,7,8,9,10)
                .map{ it * 2 }
                .filter { it % 3  == 0 }
                .map{ it+1 }
                .sum()
    }
    @Benchmark
    fun testList():Int {
        return listOf(1,2,3,4,5,6,7,8,9,10)
                .map{ it * 2 }
                .filter { it % 3  == 0 }
                .map{ it+1 }
                .sum()
    }
}
fun main() {
    val options = OptionsBuilder()
            .include(SequenceBenchmark::class.java.simpleName)
            .output("benchmark_sequence.log")
            .build()
    Runner(options).run()
}


通过基准测试得到如下的结果:

# Run complete. Total time: 00:05:23
REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.
Benchmark                        Mode  Cnt      Score     Error   Units
SequenceBenchmark.testList      thrpt   20  15924.272 ± 305.825  ops/ms
SequenceBenchmark.testSequence  thrpt   20  23099.938 ± 515.524  ops/ms


上述例子使用 OpenJDK 提供的基准测试工具 JMH 进行测试,它可以在方法层面进行基准测试。上述例子的结果表明,在多次链式调用时 Sequence 比起 List 具有更高的效率。


这是因为集合在处理每个步骤时都会返回一个新集合,Sequence 不会在每个处理步骤中创建集合。对于数据量比较大时,应该选择 Sequence。


三. Sequence VS Stream



Sequence 和 Stream 都使用的是惰性求值。


在编程语言理论中,惰性求值(英语:Lazy Evaluation),又译为惰性计算、懒惰求值,也称为传需求调用(call-by-need),是一个计算机编程中的一个概念,目的是要最小化计算机要做的工作。它有两个相关而又有区别的含意,可以表示为“延迟求值”和“最小化求值”。除可以得到性能的提升外,惰性计算的最重要的好处是它可以构造一个无限的数据类型。


下面列举了 Sequence 和 Stream 的一些区别:


特性对比 Sequence Stream
autoboxing 会发生自动装箱 对于原始类型可以避免自动装箱
parallelism 不支持 支持
跨平台 支持 Kotlin/JVM、Kotlin/JS、Kotlin/Native 等多平台 只能在 Kotlin/JVM 平台使用,并且 jvm 版本需要>=8
易用性 更简洁、支持更多的功能 使用 Collectors 进行终端操作会使 Stream 更加冗长。
性能 大多数终端操作符是 inline 函数 对于值可能不存在的情况,Sequence 支持可为空的类型,而 Stream 会创建 Optional包装器。因此会多一步的对象创建。


从易用性、性能角度来看,如果要从 Sequence 和 Stream 中作出选择的话,本人更加偏向 Sequence。

相关文章
|
24天前
|
Java 开发工具 Android开发
Kotlin语法笔记(26) -Kotlin 与 Java 共存(1)
本系列教程笔记详细讲解了Kotlin语法,适合需要深入了解Kotlin的开发者。若需快速学习Kotlin,建议查看“简洁”系列教程。本期重点介绍了Kotlin与Java的共存方式,包括属性、单例对象、默认参数方法、包方法、扩展方法以及内部类和成员的互操作性。通过这些内容,帮助你在项目中更好地结合使用这两种语言。
41 1
|
15天前
|
安全 Java 编译器
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
|
15天前
|
Java 开发工具 Android开发
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
|
15天前
|
Java 编译器 Android开发
Kotlin教程笔记(28) -Kotlin 与 Java 混编
Kotlin教程笔记(28) -Kotlin 与 Java 混编
|
20天前
|
Java API 数据处理
探索Java中的Lambda表达式与Stream API
【10月更文挑战第22天】 在Java编程中,Lambda表达式和Stream API是两个强大的功能,它们极大地简化了代码的编写和提高了开发效率。本文将深入探讨这两个概念的基本用法、优势以及在实际项目中的应用案例,帮助读者更好地理解和运用这些现代Java特性。
|
22天前
|
安全 Java Android开发
Kotlin为什么没有Java流行
Kotlin流行不起来的原因
73 1
|
26天前
|
安全 Java 程序员
Java集合之战:ArrayList vs LinkedList,谁才是你的最佳选择?
本文介绍了 Java 中常用的两个集合类 ArrayList 和 LinkedList,分析了它们的底层实现、特点及适用场景。ArrayList 基于数组,适合频繁查询;LinkedList 基于链表,适合频繁增删。文章还讨论了如何实现线程安全,推荐使用 CopyOnWriteArrayList 来提升性能。希望帮助读者选择合适的数据结构,写出更高效的代码。
49 3
|
24天前
|
Java 编译器 Android开发
Kotlin语法笔记(28) -Kotlin 与 Java 混编
本系列教程详细讲解了Kotlin语法,适合需要深入了解Kotlin的开发者。对于希望快速学习Kotlin的用户,推荐查看“简洁”系列教程。本文档重点介绍了Kotlin与Java混编的技巧,包括代码转换、类调用、ProGuard问题、Android library开发建议以及在Kotlin和Java之间互相调用的方法。
20 1
|
24天前
|
安全 Java 编译器
Kotlin语法笔记(27) -Kotlin 与 Java 共存(二)
本教程详细讲解Kotlin语法,适合希望深入了解Kotlin的开发者。若需快速入门,建议查阅“简洁”系列教程。本文重点探讨Kotlin与Java共存的高级话题,包括属性访问、空安全、泛型处理、同步机制及SAM转换等,助你在项目中逐步引入Kotlin。
20 1
|
26天前
|
Java 编译器 Android开发
Kotlin语法笔记(28) -Kotlin 与 Java 混编
Kotlin语法笔记(28) -Kotlin 与 Java 混编
25 2