【Kotlin + Spring Boot 4 从零到架构师】第 4 篇:函数与 Lambda 表达式

本系列定位:零基础入门,从 Kotlin 语法一路到 Spring Boot 4 高级架构(DDD + Modulith),适合 Java 开发者转型,也适合纯新手系统学习。


本篇你将学到

  • 函数声明的完整语法:参数、返回值、默认参数、命名参数
  • 单表达式函数的简洁写法
  • Lambda 表达式的语法与高阶函数
  • it 隐式参数的使用场景
  • Kotlin 集合操作函数:mapfilterforEachfoldreduce

学完本篇,你将能编写灵活的 Kotlin 函数,并运用 Lambda 表达式和集合操作函数写出远比 Java 简洁的数据处理代码。


一、函数声明

1.1 基本语法

Kotlin 用 fun 关键字声明函数:

// 完整语法:fun 函数名(参数名: 参数类型): 返回类型 { 函数体 }
fun add(a: Int, b: Int): Int {
    return a + b
}

// 调用
val result = add(3, 5)
println(result)    // 输出:8

对比 Java:

// Java
public static int add(int a, int b) {
    return a + b;
}

Kotlin 函数不需要写在类里面,可以直接定义在文件顶层。这在后续 Spring Boot 开发中减少了大量样板代码。

1.2 无返回值的函数

使用 Unit 表示无返回值(类似 Java 的 void),Unit 可以省略:

// 显式声明 Unit
fun greet(name: String): Unit {
    println("你好,$name!")
}

// 省略 Unit(效果相同,推荐)
fun greet(name: String) {
    println("你好,$name!")
}

1.3 单表达式函数

当函数体只有一个表达式时,可以省略花括号和 return,用 = 直接赋值:

// 普通写法
fun double(x: Int): Int {
    return x * 2
}

// 单表达式函数(推荐)
fun double(x: Int) = x * 2

// 返回类型也可以省略,编译器自动推断
fun square(x: Int) = x * x          // 推断为 Int
fun isPositive(x: Int) = x > 0      // 推断为 Boolean

使用建议:简单的一行函数用 = 写法更简洁;有复杂逻辑的函数仍用花括号。后续在 Spring Boot 的 Repository 层中,单表达式函数非常常见。


下面是函数声明的三种形式对比流程图:

Kotlin 函数声明

完整语法

无返回值函数

单表达式函数

fun add(a: Int, b: Int): Int { return a + b }

适合复杂逻辑

fun greet(name: String): Unit { ... }

Unit 可省略

fun greet(name: String) { ... }

fun double(x: Int) = x * 2

省略 return 和花括号

编译器自动推断返回类型

二、高级函数特性

2.1 默认参数

Kotlin 允许为函数参数设置默认值,调用时可以省略这些参数——这替代了 Java 中需要写多个重载函数的场景

fun createUser(
    name: String,
    age: Int = 0,
    email: String = "未填写",
    isActive: Boolean = true
) {
    println("姓名:$name,年龄:$age,邮箱:$email,状态:${if (isActive) "激活" else "未激活"}")
}

// 调用——只传必须参数
createUser("张三")
// 输出:姓名:张三,年龄:0,邮箱:未填写,状态:激活

// 调用——传部分可选参数
createUser("李四", 25)
// 输出:姓名:李四,年龄:25,邮箱:未填写,状态:激活

// 调用——传所有参数
createUser("王五", 30, "wangwu@example.com", false)
// 输出:姓名:王五,年龄:30,邮箱:wangwu@example.com,状态:未激活

对比 Java——要实现同样的效果,Java 需要写 4 个重载方法:

// Java 需要大量重载
void createUser(String name) { createUser(name, 0, "未填写", true); }
void createUser(String name, int age) { createUser(name, age, "未填写", true); }
void createUser(String name, int age, String email) { createUser(name, age, email, true); }
void createUser(String name, int age, String email, boolean isActive) { /* ... */ }

2.2 命名参数

当函数有多个参数时,可以用 参数名 = 值 的方式传参,不必按顺序:

// 只修改 email,其他用默认值
createUser(name = "赵六", email = "zhaoliu@example.com")
// 输出:姓名:赵六,年龄:0,邮箱:zhaoliu@example.com,状态:激活

// 调整参数顺序
createUser(email = "test@test.com", name = "钱七", age = 28)

在 Spring Boot 中的应用:后续定义 DTO 和 Command 类时,大量使用命名参数 + 默认参数,代码极其简洁。

2.3 可变参数(vararg)

使用 vararg 修饰符,函数可以接受任意数量的参数:

fun sum(vararg numbers: Int): Int {
    var total = 0
    for (num in numbers) {
        total += num
    }
    return total
}

// 传入任意数量的参数
println(sum(1, 2, 3))           // 6
println(sum(1, 2, 3, 4, 5))     // 15
println(sum())                  // 0

// 如果已经有一个数组,用展开操作符 * 传入
val numbers = intArrayOf(10, 20, 30)
println(sum(*numbers))          // 60

vararg 相当于 Java 的 int... 可变参数。在后续 Spring Boot 开发中,批量删除接口经常使用 @PathVariable vararg ids: Long


三、Lambda 表达式

Lambda 表达式是函数式编程的核心。简单来说,Lambda 就是一段可以作为参数传递的代码块,也可以被称为「匿名函数」。

3.1 Lambda 语法

完整的 Lambda 语法:

{ 参数列表 -> 函数体 }
// 完整写法:接受两个 Int 参数,返回它们的和
val sum: (Int, Int) -> Int = { a: Int, b: Int -> a + b }

// 调用
println(sum(3, 5))     // 输出:8

// 类型推断简化
val sum2 = { a: Int, b: Int -> a + b }

// 进一步简化(已知类型时)
val sum3: (Int, Int) -> Int = { a, b -> a + b }

3.2 无参数的 Lambda

val sayHello: () -> Unit = { println("Hello!") }
sayHello()    // 输出:Hello!

3.3 单参数 Lambda 与 it 关键字

当 Lambda 只有一个参数时,可以省略参数声明,用 it 关键字代替:

val numbers = listOf(1, 2, 3, 4, 5)

// 完整写法
val doubled = numbers.map { n: Int -> n * 2 }

// 简化写法:用 it 代替唯一参数
val doubled2 = numbers.map { it * 2 }

println(doubled2)    // 输出:[2, 4, 6, 8, 10]

it 是 Kotlin 中一个非常重要的约定——当 Lambda 只有一个参数时,it 自动指代该参数。这大大简化了集合操作代码。

3.4 Lambda 作为函数参数(高阶函数)

高阶函数就是接受 Lambda 作为参数或返回 Lambda 的函数:

// 定义高阶函数:接受一个 Lambda 作为操作
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
    return operation(a, b)
}

// 调用——传入不同的 Lambda
val result1 = calculate(10, 3) { x, y -> x + y }    // 加法 → 13
val result2 = calculate(10, 3) { x, y -> x - y }    // 减法 → 7
val result3 = calculate(10, 3) { x, y -> x * y }    // 乘法 → 30

println("$result1, $result2, $result3")    // 输出:13, 7, 30

注意语法细节:当函数的最后一个参数是 Lambda 时,Lambda 可以写在圆括号外面。如果 Lambda 是唯一的参数,圆括号也可以省略。这是 Kotlin 最优雅的语法糖之一。

3.5 从 Lambda 返回值

Lambda 的最后一行表达式就是返回值(和 if/when 表达式规则一致):

val process: (Int) -> String = { num ->
    if (num > 0) {
        "正数"
    } else if (num < 0) {
        "负数"
    } else {
        "零"
    }            // ← 最后一行就是返回值
}

println(process(42))     // 正数
println(process(-1))     // 负数
println(process(0))      // 零

下面是高阶函数调用过程的时序图:

"Lambda { x, y ->> x + y }" "高阶函数 calculate()" "调用方" "Lambda { x, y ->> x + y }" "高阶函数 calculate()" "调用方" 尾随 Lambda 语法:最后一个参数是 Lambda 时可写在括号外 calculate(10, 3) { x, y ->> x + y } 传入参数 10, 3 返回 13 返回 13

四、集合操作函数

Lambda 表达式最常见的用途就是配合集合操作函数使用。这是 Kotlin 相对于 Java 最大的简洁性优势之一。

4.1 核心集合操作函数

以下操作都配合 Lambda 使用:

函数作用Java 对应
map将每个元素转换为新元素stream().map()
filter过滤符合条件的元素stream().filter()
forEach遍历每个元素stream().forEach()
find查找第一个匹配元素stream().findFirst()
any是否有元素满足条件stream().anyMatch()
all是否所有元素都满足条件stream().allMatch()
none是否没有元素满足条件stream().noneMatch()
count统计满足条件的元素数stream().filter().count()
sortedBy按条件排序stream().sorted()
groupBy按条件分组Collectors.groupingBy()
fold从初始值开始累计stream().reduce()
reduce累计所有元素stream().reduce()

4.2 map — 转换元素

val numbers = listOf(1, 2, 3, 4, 5)

// 每个元素乘以 2
val doubled = numbers.map { it * 2 }
println(doubled)    // [2, 4, 6, 8, 10]

// 转换类型:Int → String
val strings = numbers.map { "数字:$it" }
println(strings)    // [数字:1, 数字:2, 数字:3, 数字:4, 数字:5]

// 提取对象属性
data class Product(val name: String, val price: Double)
val products = listOf(
    Product("键盘", 199.0),
    Product("鼠标", 89.0),
    Product("显示器", 1299.0)
)
val names = products.map { it.name }
println(names)      // [键盘, 鼠标, 显示器]
val prices = products.map { it.price }
println(prices)     // [199.0, 89.0, 1299.0]

4.3 filter — 过滤元素

val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

// 过滤偶数
val evens = numbers.filter { it % 2 == 0 }
println(evens)      // [2, 4, 6, 8, 10]

// 过滤大于 5 的数
val big = numbers.filter { it > 5 }
println(big)        // [6, 7, 8, 9, 10]

// 过滤商品中价格 > 100 的
val products = listOf(
    Product("键盘", 199.0),
    Product("鼠标", 89.0),
    Product("显示器", 1299.0)
)
val expensive = products.filter { it.price > 100.0 }
println(expensive.map { it.name })   // [键盘, 显示器]

4.4 链式调用

集合操作函数可以链式调用,像流水线一样处理数据:

val products = listOf(
    Product("键盘", 199.0),
    Product("鼠标", 89.0),
    Product("显示器", 1299.0),
    Product("耳机", 299.0),
    Product("鼠标垫", 19.0)
)

// 链式操作:筛选 → 转换 → 排序
val result = products
    .filter { it.price > 50.0 }                     // 过滤价格 > 50
    .map { it.name to it.price }                    // 转换为 Pair(名称, 价格)
    .sortedByDescending { it.second }               // 按价格降序排列

result.forEach { (name, price) ->
    println("$name:¥$price")
}
// 输出:
// 显示器:¥1299.0
// 耳机:¥299.0
// 键盘:¥199.0

对比 Java 等价代码:

// Java 等价实现——明显更繁琐
List<Map.Entry<String, Double>> result = products.stream()
    .filter(p -> p.getPrice() > 50.0)
    .map(p -> Map.entry(p.getName(), p.getPrice()))
    .sorted((a, b) -> Double.compare(b.getValue(), a.getValue()))
    .collect(Collectors.toList());

4.5 groupBy — 分组

data class Student(val name: String, val grade: String, val score: Int)

val students = listOf(
    Student("张三", "一班", 90),
    Student("李四", "二班", 85),
    Student("王五", "一班", 78),
    Student("赵六", "二班", 92),
    Student("钱七", "一班", 65)
)

// 按班级分组
val byGrade = students.groupBy { it.grade }
byGrade.forEach { (grade, studentList) ->
    println("$grade:${studentList.map { it.name }}")
}
// 输出:
// 一班:[张三, 王五, 钱七]
// 二班:[李四, 赵六]

// 按是否及格分组
val byPass = students.groupBy { it.score >= 60 }
println("及格:${byPass[true]?.map { it.name }}")   // [张三, 李四, 王五, 赵六, 钱七]

4.6 fold 与 reduce — 聚合

val numbers = listOf(1, 2, 3, 4, 5)

// reduce:无初始值,从第一个元素开始累计
val sum = numbers.reduce { acc, num -> acc + num }
println(sum)    // 15

// fold:有初始值,更灵活
val sumFrom10 = numbers.fold(10) { acc, num -> acc + num }
println(sumFrom10)    // 25(10 + 1 + 2 + 3 + 4 + 5)

// fold 可以累计为不同类型
val concatenated = numbers.fold("数字:") { acc, num -> "$acc$num " }
println(concatenated)    // 数字:1 2 3 4 5

4.7 常用判断函数

val numbers = listOf(1, 2, 3, 4, 5)

// any:是否有满足条件的元素
println(numbers.any { it > 3 })       // true
println(numbers.any { it > 10 })      // false

// all:是否所有元素都满足条件
println(numbers.all { it > 0 })       // true
println(numbers.all { it > 3 })       // false

// none:是否没有元素满足条件
println(numbers.none { it < 0 })      // true

// count:统计满足条件的元素数
println(numbers.count { it % 2 == 0 })    // 2

// find:查找第一个匹配元素(返回可空类型)
val firstEven = numbers.find { it % 2 == 0 }
println(firstEven)    // 2

下面是集合操作链式调用的数据流图:

原始集合
products

filter
价格 > 50

map
转换为 (name, price)

sortedByDescending
按价格降序

forEach
输出结果

显示器:¥1299.0
耳机:¥299.0
键盘:¥199.0

五、实战练习

练习一:商品数据处理

data class Product(val name: String, val category: String, val price: Double, val stock: Int)

fun main() {
    val products = listOf(
        Product("机械键盘", "外设", 299.0, 50),
        Product("蓝牙鼠标", "外设", 89.0, 0),
        Product("27寸显示器", "显示", 1599.0, 15),
        Product("USB-C 扩展坞", "配件", 199.0, 100),
        Product("无线耳机", "音频", 599.0, 30),
        Product("有线耳机", "音频", 49.0, 0)
    )

    // 1. 找出所有有库存的商品名称
    val inStock = products
        .filter { it.stock > 0 }
        .map { it.name }
    println("有库存:$inStock")
    // 输出:有库存:[机械键盘, 27寸显示器, USB-C 扩展坞, 无线耳机]

    // 2. 按类别分组,计算每类商品的平均价格
    val avgPriceByCategory = products
        .groupBy { it.category }
        .mapValues { (_, list) ->
            list.map { it.price }.average()
        }
    avgPriceByCategory.forEach { (category, avg) ->
        println("$category 平均价格:¥${"%.2f".format(avg)}")
    }
    // 输出:
    // 外设 平均价格:¥194.00
    // 显示 平均价格:¥1599.00
    // 配件 平均价格:¥199.00
    // 音频 平均价格:¥324.00

    // 3. 找到价格最高的商品
    val mostExpensive = products.maxByOrNull { it.price }
    println("最贵商品:${mostExpensive?.name}")    // 27寸显示器

    // 4. 计算库存总值
    val totalValue = products.fold(0.0) { acc, p ->
        acc + p.price * p.stock
    }
    println("库存总值:¥$totalValue")
}

练习二:单词统计器

fun main() {
    val sentence = "kotlin is great and kotlin is powerful and kotlin is easy"

    // 统计每个单词出现的次数
    val wordCount = sentence
        .split(" ")
        .groupBy { it }
        .mapValues { it.value.size }
        .toList()
        .sortedByDescending { it.second }

    wordCount.forEach { (word, count) ->
        println("$word: $count 次")
    }
    // 输出:
    // kotlin: 3 次
    // is: 3 次
    // and: 2 次
    // great: 1 次
    // powerful: 1 次
    // easy: 1 次
}

阅读建议:这张图总结了全篇所有知识点及其层级关系。你可以对照此图逐节学习,也可以在学习完某节后回顾图中对应分支,检查自己是否掌握了所有子知识点。

本篇小结

知识点核心内容
函数声明fun name(params): ReturnType { body }
单表达式函数fun double(x: Int) = x * 2
默认参数fun f(name: String, age: Int = 0)
命名参数f(name = "Tom", age = 20)
可变参数fun sum(vararg nums: Int)
Lambda 语法{ a, b -> a + b }
it 关键字单参数 Lambda 的隐式参数名
高阶函数接受 Lambda 作为参数的函数
尾随 Lambda最后一个参数是 Lambda 时可写在括号外
map转换每个元素
filter过滤元素
groupBy按条件分组
fold / reduce聚合累计

下篇预告

第 5 篇:类与对象(面向对象基础)

Kotlin 的 data class 用一行代码就能替代 Java 几十行的 POJO。下一篇正式进入面向对象编程,学习类的声明、属性、构造函数、可见性修饰符等核心概念。


如果本篇内容对你有帮助,欢迎点赞收藏!有任何疑问,欢迎在评论区交流。

更多推荐