Kotlin 集合与函数式编程

Java 8 引入了 Stream API,Kotlin 则在内置的集合操作库中提供了更丰富的函数式编程支持。更重要的是,Kotlin 的作用域函数(scope functions)是 Java 中没有的概念——它们是代码组织的重要工具。

1. 集合创建:告别 new ArrayList<>()

Java:

List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
 
// Java 9+
List.of("a", "b");       // 不可变
Map.of("k1", "v1");

Kotlin:

val list = listOf("a", "b")          // 不可变 List
val mutableList = mutableListOf("a")  // 可变 List
val map = mapOf("k1" to "v1")        // 不可变 Map
val set = setOf("a", "b")            // 不可变 Set

NOTE

重要哲学 Kotlin 区分可变不可变集合。默认使用不可变(listOfmapOf),需要修改时才用可变版本。这与 Java 的「集合默认都可变」完全不同。

2. 链式集合操作

Kotlin 的集合操作不需要 .stream()——直接在集合上调用即可,并且比 Java Stream API 更丰富。

Java Stream:

list.stream()
    .filter(s -> s.length() > 3)
    .map(String::toUpperCase)
    .sorted()
    .collect(Collectors.toList());

Kotlin:

list.filter { it.length > 3 }
    .map { it.uppercase() }
    .sorted()
    .toList()
// 不需要 .stream(),直接调用

Kotlin 的关键改进:

  • 不需要 .stream() — 集合操作是内置的
  • it — 单参数 lambda 的隐式名称,不用写 s ->
  • 更丰富的操作符chunkedwindowedzipassociateBy

常用操作速览

操作作用Kotlin 示例
filter过滤list.filter { it > 5 }
map转换list.map { it.toString() }
flatMap展平嵌套集合list.flatMap { it.subList() }
groupBy分组list.groupBy { it.category }
associateBy转 Maplist.associateBy { it.id }
chunked按大小分块list.chunked(3)
zip两个集合配对names.zip(ages)
distinct去重list.distinct()
sortedBy按属性排序list.sortedBy { it.name }

TIP

🔑 PICO Demo 实战AnimationModels.kt 中的写法:

companion object {
    fun getList(): List<NavigationState> =
        NavigationState.entries.toList()
}

entries 是 Kotlin 1.9+ 中替代 values() 的方式。

3. 作用域函数 — Kotlin 的独门武器

作用域函数是 Kotlin 中最常用的语法糖之一。一共有 5 个:letapplyrunwithalso

3.1 with — “在这个对象上下文中”

// PICO Demo 中最常见的用法
fun mainApp(scope: SpatialAppScope) =
    with(scope) {
        // 这里直接调用 scope 的方法,不需要 scope.xxx
        DefaultWindowContainer {
            PicoTheme {
                // ...
            }
        }
    }

with 把接收者作为 lambda 的上下文,让你直接调用接收者的方法而无需重复写 scope.

3.2 apply — “配置对象并返回它”

// Java 方式:先 new,然后 set,最后返回
Entity entity = new Entity("myId");
entity.setName("MyName");
entity.setParent(parent);
 
// Kotlin apply:创建 + 配置 = 一条表达式
val entity = Entity().apply {
    name = "MyName"
    parent = parentEntity
}
// apply 返回配置后的对象本身

TIP

🔑 PICO Demo 实战

// RoomLighting.kt
viewModelScope.async {
    IblEntity().apply {
        initialize()
    }
}

apply 让对象创建和初始化在一条表达式中完成。

3.3 let — “对非空值执行操作”

// Java 式空检查
if (data != null) {
    process(data);
}
 
// Kotlin 的 let + 安全调用
data?.let { processed ->
    process(processed)
}
// 或者用隐式 it
data?.let { process(it) }

let 在 PICO Demo 中最常见的用途是与安全调用运算符 ?. 配合使用。

3.4 run — “计算一个值”

val label = run {
    val formatted = formatData()
    val timestamp = getTimestamp()
    "$formatted @ $timestamp"
}
 
// run 也可以作为带接收者的 lambda——等价于 with,但返回值是 lambda 的最后一行
entity.run {
    // this 是 entity
    position = Vector3(0f, 1f, 0f)
    rotation = Quat.identity
}

3.5 also — “执行副作用”

// also 返回原始对象,但允许你执行额外操作
val entity = Entity().also {
    Log.d(TAG, "Created entity: ${it.id}")
    statsCounter.increment()
}

速查表

函数上下文对象返回值典型用途
letitlambda 结果空安全执行、局部变量限定
applythis上下文对象本身对象配置(Builder 风格)
runthislambda 结果对象配置 + 返回值
withthislambda 结果对同一对象执行多个操作(非扩展函数)
alsoit上下文对象本身副作用、日志、调试

4. Sequence — 惰性集合

Kotlin 的 Sequence 类似 Java 的 Stream——惰性求值,避免中间集合创建。

Java Stream:

list.stream()
    .filter(x -> x > 3)
    .map(x -> x * 2)
    .limit(5)
    .collect(toList());

Kotlin Sequence:

list.asSequence()
    .filter { it > 3 }
    .map { it * 2 }
    .take(5)
    .toList()

WARNING

注意 对于小集合,直接使用 list.filter {...}.map {...} 更简单。只有在链式操作很多、或者处理大集合时才需要 asSequence()

5. PICO Demo 中的函数式模式

模式:用 Map/List 存储多种方案

// PhysicsViewModel.kt — 用 mapOf 存储状态映射
data class PhysicsUiState(
    val playPhase: PlayPhase = PlayPhase.IDLE,
    val hint: String = "",
    val isHintShow: Boolean = false
)
 
// spatalui demo — 用 data class List 存储 UI 数据
data class CardData(
    @DrawableRes val image: Int,
    @StringRes val title: Int,
    @StringRes val content: Int,
    val color: Color
)
 
val demoData = listOf(
    CardData(...),
    CardData(...),
)

模式:with(scope) + DSL 构建 UI

// 7 个 Demo 项目的 mainApp 统一模式
fun mainApp(scope: SpatialAppScope) =
    with(scope) {
        DefaultWindowContainer {
            PicoTheme {
                HomePage()
            }
        }
    }
 
// with(scope) 让 scope 变成 this
// DefaultWindowContainer { } 相当于 this.DefaultWindowContainer { }
// PicoTheme { } 相当于 this.PicoTheme { }

模式:协程 + 集合 + 函数式

// AudioResourceStore.kt — 预加载所有音频文件
suspend fun preloadAudio() = coroutineScope {
    AUDIO_NAME_TO_PATH
        .filterKeys { it !in audioResourceMap }
        .map { (name, path) ->
            async(Dispatchers.IO) {
                name to AudioResource.load(name, path, LoadType.FROM_ASSETS)
            }
        }
        .awaitAll()
        .forEach { (name, res) -> audioResourceMap[name] = res }
}

看到这里的组合了吗?集合操作(filterKeys + map) + 协程(async + awaitAll) + 作用域函数(forEach) —— Kotlin 的三大特性融合在一起。

快速练习

用 Kotlin 将以下 Java 代码重写:

// Java
List<String> result = new ArrayList<>();
for (User user : userList) {
    if (user.getAge() >= 18) {
        result.add(user.getName().toUpperCase());
    }
}
Collections.sort(result);
点击查看答案
// Kotlin
val result = userList
    .filter { it.age >= 18 }
    .map { it.name.uppercase() }
    .sorted()

下一课预告

下一课我们将进入实战环节——综合解读 PICO 8 个 Demo 项目的 Kotlin 代码模式,把所有学到的知识串联起来。

INFO

参考资料


上一课: 0002-kotlin-coroutines | 下一课: 0004-pico-demo-code-analysis