【Kotlin】Kotlin的stream流编程浅析

  Kotlin是一门由JetBrains公司开发的静态类型JVM语言,其可以与Java无缝集成。与Java相比,Kotlin的语法更简洁、更具表达性,而且提供了更多的特性,比如,高阶函数、操作符重载、字符串模板。今天要浅析的stream流操作就来源于java8当中的特性。

一、kotlin的stream流具有的部分操作函数和操作符

操作类型 操作名称 操作解释
转换操作 filter 根据给定的条件过滤流中的元素
map 将流中的每个元素应用给定的转换函数
flatMap 将流中的每个元素转换为一个流,并将所有流中的元素合并为一个流
distinct 去除流中的重复元素
sorted 根据给定的比较器对流中的元素进行排序
limit 限制流的大小为指定的数量
终止操作 forEach 对流中的每个元素应用给定的操作
toList 将流中的元素转换为列表
toSet 将流中的元素转换为集合
toMap 将流中的元素转换为映射
reduce 根据给定的操作符对流中的元素进行累积计算
collect 对流中的元素进行收集操作,可以根据自定义的收集器进行指定
  除了上面表格中所列出来的操作以外,kotlin的流式操作还支持诸如 `groupBy`、`associateBy`、`associate`、`count`、`any`、`all`、`find`、`maxBy`、`minBy` 等。其实仔细观察上面的表格,我们可以很轻易地发现,kotlin中的stream流操作几乎和java中的一模一样。既然kotlin作为一门和java不同的语言,它的流式操作肯定有其独到之处。接下来我们就来介绍一下kotlin中stream流和Java中的stream流的区别。

二、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中常用的filtermapforeach等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 在设计时考虑了更多的便利性,它提供了一些额外的操作符,如 groupByassociatepartition 等,用于更方便地进行分组、关联和拆分操作。   Kotlin 中的集合操作是基于扩展函数来实现的,使得操作可以直接应用于集合本身,使代码更加简洁。而 Java 中的 Stream API 是通过流水线操作来处理集合的。所以Kotlin 的函数式操作符可以直接使用 Kotlin 标准库中的集合类型,如 ListSetMap。而 Java 的函数式操作符需要应用于 Java 的集合类型,如 ListSetMap。   在 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() 方法将集合转换为序列,然后进行流式操作。

end
  • 作者:dicraft(联系作者)
  • 更新时间:2024-01-02 11:14
  • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 转载声明:如果是转载栈主转载的文章,请附上原文链接
  • 评论

    新增邮件回复功能,回复将会通过邮件形式提醒,请填写有效的邮件!