Kotlin是一门由JetBrains公司开发的静态类型JVM语言,其可以与Java无缝集成。与Java相比,Kotlin的语法更简洁、更具表达性,而且提供了更多的特性,比如,高阶函数、操作符重载、字符串模板。今天要浅析的stream流操作就来源于java8当中的特性。
一、kotlin的stream流具有的部分操作函数和操作符
操作类型 | 操作名称 | 操作解释 |
转换操作 | filter | 根据给定的条件过滤流中的元素 |
map | 将流中的每个元素应用给定的转换函数 | |
flatMap | 将流中的每个元素转换为一个流,并将所有流中的元素合并为一个流 | |
distinct | 去除流中的重复元素 | |
sorted | 根据给定的比较器对流中的元素进行排序 | |
limit | 限制流的大小为指定的数量 | |
终止操作 | forEach | 对流中的每个元素应用给定的操作 |
toList | 将流中的元素转换为列表 | |
toSet | 将流中的元素转换为集合 | |
toMap | 将流中的元素转换为映射 | |
reduce | 根据给定的操作符对流中的元素进行累积计算 | |
collect | 对流中的元素进行收集操作,可以根据自定义的收集器进行指定 |
二、kotlin和java中的stream流有什么区别
我们可以从语法差异、空值处理、函数式操作符等方面来一一解析。
1.语法差异
在 Kotlin 中,Stream 流操作被称为集合操作(Collection Operations),而在 Java 中是通过 Stream API 来实现流操作。
// 创建流并过滤
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> filteredNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// 映射操作
List<String> names = Arrays.asList("John", "Jane", "Tom");
List<Integer> nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
// 使用终端操作 forEach
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
.forEach(System.out::println);
// 创建流并过滤
val numbers = listOf(1, 2, 3, 4, 5)
val filteredNumbers = numbers.stream()
.filter { it % 2 == 0 }
.toList()
// 映射操作
val names = listOf("John", "Jane", "Tom")
val nameLengths = names.stream()
.map { it.length }
.toList()
// 使用终端操作 forEach
val numbers = listOf(1, 2, 3, 4, 5)
numbers.stream()
.forEach { println(it) }
观察上面的两种stream流写法,我们可以很清晰的就发现尽管两者在语义上似乎大差不差,但是在java中常用的filter
、map
、foreach
等api在kotlin中实现时,不再需要使用lambda表达式去编写匿名函数了。现在在kotlin中,通过kotlin提供的语法糖,我们可以使用更加简洁的语法完成想要做的事情。Kotlin 中的 Lambda 表达式不需要显示指定参数类型,可以使用 it
来引用单个参数,同时 Kotlin 中的流式操作可以省略点符号,直接使用方法调用语法。
2.空值处理
在 Java 中的 Stream 流中,如果集合中存在空值(null),那么在进行流式操作时,会抛出 NullPointerException
异常。这意味着你需要确保集合中不包含空值,否则会导致异常。
而在 Kotlin 中,使用 stream()
方法将集合转换为 Stream 对象后,并没有特定的空值处理机制。如果集合中存在空值,则在进行 Stream 操作时,可能会引发 NullPointerException
异常。
Kotlin 提供了一种更加宽容的处理方式,可以通过额外的操作来处理空值。我们可以使用 filterNotNull()
方法在流操作前过滤掉空值,或使用默认值替代空值。
下面的示例就可以很清晰易懂的说明上面的内容。
List<String> names = Arrays.asList("John", null, "Jane");
names.stream()
.forEach(System.out::println); // 抛出 NullPointerException
val names = listOf("John", null, "Jane")
// 使用默认值替代空值
val namesWithDefault = names.stream()
.map { it ?: "Unknown" }
.toList()
3.函数式操作符
由于 Kotlin 在设计时考虑了更多的便利性,它提供了一些额外的操作符,如 groupBy
、associate
、partition
等,用于更方便地进行分组、关联和拆分操作。
Kotlin 中的集合操作是基于扩展函数来实现的,使得操作可以直接应用于集合本身,使代码更加简洁。而 Java 中的 Stream API 是通过流水线操作来处理集合的。所以Kotlin 的函数式操作符可以直接使用 Kotlin 标准库中的集合类型,如 List
、Set
、Map
。而 Java 的函数式操作符需要应用于 Java 的集合类型,如 List
、Set
、Map
。
在 Kotlin 中,集合操作的执行是惰性的,只有在终端操作调用时才会执行。而在 Java 中,默认情况下,Stream 的操作都是eager执行的。
三、kotlin流式操作的特性
1.链式操作
可以按照操作的顺序依次连接多个操作,使代码更加简洁和可读。
2.惰性计算(lazy)
Kotlin 的流操作是惰性计算的,只有在终端操作被调用时才会实际执行中间的操作。这种惰性求值的策略能够提高性能,避免不必要的计算。也就是说,在流操作执行之前,并不会立即执行计算。而是在 toList()
、forEach()
等方法调用时才会触发流中的元素进行计算。
3.操作符扩展
Kotlin 的流操作是通过扩展函数来实现的,这意味着可以为任何类型的集合或数据源定义自定义的操作符,并与标准操作符无缝组合使用。
常用的操作符包括 filter()
、map()
、distinct()
、sorted()
、groupBy()
、reduce()
等。
5. 空值处理
Kotlin 的流式操作符提供了对空值的灵活处理方式。例如可以使用 filterNotNull()
过滤掉空值,或使用 elvis 运算符 ?:
为空值提供默认值。
6. 序列支持
除了使用 Java 8 中的 Stream 流,Kotlin 还提供了序列(Sequence)的概念。序列是一种类似于 Stream 流的惰性计算机制,可以更高效地处理大量数据。使用序列可以通过 asSequence()
方法将集合转换为序列,然后进行流式操作。