16.3 Stream流式编程
标签(空格分隔): kotlin 出版
书名:Kotlin基础教程
作者:陈小默
Stream介绍
java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。当最终操作发生时则次操作序列结束。Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set。Stream的操作可以串行执行或者并行执行。
为了更好的说明Stream的工作流程,我们需要使用一个Bean类和一个工具方法
class Person(
var name: String,
var age: Int,
var sex: Boolean//true表示女,false表示男
) {
override fun toString(): String {
return "Person(name='$name', age=$age, sex=${if (sex) "女" else "男"})"
}
}
/**
* 该方法用来产生个数为num的随机Bean对象列表
*/
fun creator(num: Int): ArrayList<Person> {
val array = ArrayList<Person>()
for (i in 0..num) {
array.add(Person("name$i"
, (Math.random() * 10 + 20).toInt()//随机年龄在20-30岁之间
, (Math.random() * 10 + 20).toInt() % 2 == 1)//随机性别
)
}
return array
}
由于在本节编辑时,Kotlin并没有支持此Java8版本中新增的stream()方法,所以这里我们使用扩展为ArrayList增加一个Stream方法
fun ArrayList<Person>.stream(): Stream<Person> {
return StreamSupport.stream(Spliterators.spliterator(this, 0), false)
}
以下示例中的所有操作在main方法中完成
fun main(args: Array<String>) {
var people = creator(20)
...
}
中间操作方法
Filter 过滤
过滤通过一个predicate接口来过滤并只保留符合条件的元素。非Lambda示例(本章结束时将会附上完成代码):
people.stream().filter(object : Predicate<Person>{
override fun test(t: Person): Boolean {
return !t.sex
}
})
Stream通过Predicate的test方法的返回值来决定是否保留该元素,所以当返回值为false时,该元素不会进行下一轮操作。该示例的作用是保留所有男性元素。经过Lambda简化后的代码为
people.stream().filter { p -> !p.sex }
Sort 排序
通过一个Comparator接口对一组数据进行排序。
people.stream().sorted(object : Comparator<Person> {
override fun compare(p1: Person, p2: Person): Int {
return p1.age - p2.age
}
})
这里排序是通过将两个元素按照compare方法规定的方式进行比较来进行排序,这里的作用是将所有元素按照年龄升序排序。Lambda化的实现方式为:
people.stream().sorted { p1, p2 -> p1.age - p2.age }
Map 映射
通过map映射,能够很轻松的按照Function接口依次将元素变换为另一种类型,下面将使用两种方式展现如何将Person对象转换为字符串数据:
//原始方式
people.stream().map(object : Function<Person, String> {
override fun apply(t: Person?): String {
return t.toString()
}
})
//Lambda方式
people.stream().map { Person::toString }
最终操作
Match 匹配
匹配操作的目的是从判断Stream中的数据是否满足Predicate接口指定的规则。有如下三种匹配方式:
- 任意匹配:只要Stream中有一个元素满足要求就算匹配成功
- 完全匹配:只有Stream中的全部元素满足要求才算匹配成功
- 完全不匹配:当Stream中的没有一个元素满足要求是匹配成功
这里使用任意匹配举一个例子:
//原始方式
val isMatch: Boolean = people.stream().
anyMatch(object : Predicate<Person> {
override fun test(p: Person): Boolean {
return p.age == 25
}
})
//Lambda方式
val isMatchWithLambda = people.stream().anyMatch { p -> p.age == 25 }
Count 计数
该操作返回Stream中经过多个中间操作后剩余的元素的个数,其类型是Long。以下通过一个示例查询25岁元素的个数:
val counter: Long = people.stream().filter { p -> p.age == 25 }.count()
println(counter)
Reduce 规约
该操作通过一个BinaryOperator接口将Stream中多个元素按照约定方法组合成一个元素后按照Optional接口标准返回。
经过上述介绍,我们已经大体知道这些操作的用法了,现在我们通过一个练习总结刚才的操作:
练习:随机产生20条数据,仅打印年龄最大的5个女性的名字。
people.stream().sorted { p1, p2 -> p1.age - p2.age }
.filter { p -> p.sex }
.limit(5)
.map(Person::name)
.forEach { str -> println(str) }
并行Streams
Stream的运行模式有串行和并行两种方式。串行方式其实就是在哪个线程创建就在哪个线程执行,而并行方式在运行之初会开启一个线程,然后将自己剩下的操作在该线程中完成。通过在子线程中运行耗时操作可以极大地提升程序的运行效率。在Java中,我们只需要将启动方式从stream()方法改为parallelStream()。在当前Kotlin对于Java8支持不完善的情况下,我们需要自己添加如下方法:
fun ArrayList<Person>.方法改为parallelStream(): Stream<Person> {
return StreamSupport.stream(Spliterators.spliterator(this, 0), true)
}
可以发现,并行方法仅仅是更改了串行方法的一个标志位。