学习Kotlin语法(四)
简介
在上一节,我们对Kotlin中函数的相关知识有了大致的了解,本章节我们将去了解一些Kotlin中的作用域函数。
目录
- let:处理可空对象,链式操作
- run:对象配置 + 计算返回值
- with:对非空对象执行多个操作
- apply:初始化对象配置
- also:附加操作(如打印日志)
Kotlin 中的作用域函数(Scope Functions)是 let、run、with、apply、also,它们可以简化对对象的操作,使代码更简洁
let:处理可空对象,链式操作
在 Kotlin 中,let 函数通过与 安全调用操作符 ?. 结合使用,优雅地处理可空对象。以下是详细解释:
let 处理可空对象的基本原理
语法结构:
可空对象?.let{ ... }关键机制:
如果对象 非空,
?.会触发let代码块执行,对象作为参数(默认为it)传递给 lambda如果对象 为空,
?.会跳过let代码块,整个表达式返回null,且 lambda 中的代码 不会执行fun main() {
val nullableString: String? = "Hello" // 在类型后面声明 ? 为 可空对象
val length = nullableString?.let {
println("执行 let: 对象非空, 内容为 $it") // 非空时才会打印 输出: 执行 let: 对象非空, 内容为 Hello
it.length // 返回长度 (最后表达式的结果)
}
println("字符串长度: $length") // 输出: 字符串长度: 5 // 对象为空的时候
val nullString: String? = null
val nullResult = nullString?.let {
println("因为对象为空,这里不会执行") // 被跳过
it.length
}
println("空对象的结果是: $nullResult") // 输出: 空对象的结果是: null }
如果省略安全调用符
?.(错误示范)直接调用
let(不加?.) 可能会导致空指针异常(NPE)fun main() {
val str: String? = null
str.let { // 编译警告:此处可能抛出 NPE!
println(it!!.length) // 如果 str 为 null,
}
}
/**
* Exception in thread "main" java.lang.NullPointerException
* at MainKt.main(main.kt:4)
* at MainKt.main(main.kt)
* */
所以必须使用
?.let处理可空对象链式操作与默认值处理
结合 Elvis 操作符
?:,可以为let的返回结果提供默认值:fun main() {
val input: String? = null
val processed = input?.let {
it.uppercase() // 非空时转换成大写
} ?: "DEFAULT" // 如何 input 为 null 返回 'DEFAULT'
println(processed) // 输出: DEFAULT
}
经典使用场景
避免空检查嵌套
data class User(val name: String = "") fun main() {
val user = User("NPC")
// 传统空检查(繁琐)
if (user != null) {
if (user.name != null) {
println(user.name.length) // 输出: 3
}
}
// 使用 ?.let(简洁)
user?.name?.let { println(it.length) } // 输出: 3
}
数据转换
fun main() {
val number: Int? = "123".toIntOrNull() println(number) // 输出: 123 val squared = number?.let { it * it } // 非空时计算平方,否则返回 null println(squared) // 输出: 15129
}
副作用操作(如打印日志)
fun main() {
val data = "Hello Kotlin"
data?.let {
println("处理数据: $it")
}
}
run:对象配置 + 计算返回值
在 Kotlin 中,run 是一个灵活的作用域函数,它有两种形式:扩展函数和非扩展函数。它的核心用途是在对象的上下文中执行代码块,并返回 lambda 表达式的结果。以下是 run 的详细讲解
run 的两种形式
形式1: 拓展函数(对象引用)
对象.run {
// 代码块:通过 this 访问对象
// 最后一行作为返回值
}
上下文: 代码内使用
this引用对象(可省略)返回值: lambda 的最后一行结果
形式2: 非拓展函数(独立作用域)
run {
// 独立代码块(无需对象)
// 最后一行作为返回值
}
- 用途: 创建一个临时作用域,避免变量污染外部环境
示例代码
扩展函数(操作对象并返回结果)
在
Car对象上下文中配置属性,并返回一个描述状态的字符串。data class Car(var speed: Int = 0, var isEngineOn: Boolean = false) fun main() {
val carStatus = Car().run {
this.speed = 100 // 直接访问属性(this 可省略)
isEngineOn = true // 修改对象状态
"车速: $speed km/h,引擎状态: ${if (isEngineOn) "开启" else "关闭"}" // 返回字符串
} println(carStatus) // 输出: 车速: 100 km/h,引擎状态: 开启
}
处理可空对象(结合空安全调用?.run)
仅在对象非空时执行代码块,避免空指针异常。
fun printLengthIfNotNull(input: String?) {
input?.run {
println("字符串内容: $this, 长度: $length") // this 指代 input 对象
} ?: println("输入为空")
}
fun main() {
printLengthIfNotNull("Hello, Kotlin") // 输出: 字符串内容: Hello, Kotlin, 长度: 13
printLengthIfNotNull(null) // 输出: 输入为空
}
非扩展形式(独立作用域)
封装临时计算逻辑,避免变量
a和b泄漏到外部作用域。fun main() {
val result = run {
val a = 10
val b = 20
a + b // 返回计算结果
}
println("计算结果:$result") // 输出:计算结果:30
}
高级用法
链式调用多个
runfun main() {
val message = "Kotlin"
.run { uppercase() } // 转换为大写
.run { "Message: $this" } // 添加前缀
println(message) // 输出: Message: KOTLIN
}
与
apply结合使用data class Config(var host: String = "", var port: Int = 0) fun main() {
val config = Config().apply {
host = "127.0.0.1" // 初始化配置
port = 8080
}.run {
"服务器地址: $host:$port" // 转换为连接字符串
}
println(config) // 输出: 服务器地址: 127.0.0.1:8080
}
apply:初始化对象配置
在 Kotlin 中,apply 是一个常用的作用域函数,专门用于对象的初始化或配置。它通过简洁的语法让你在一个代码块中完成对对象属性的设置,并最终返回对象本身,以下是 apply 的详细讲解
基本语法
val 对象 = 原始对象.apply {
// 在此配置对象的属性或调用方法
this.property = value // this 可省略
method()
// ...
}
// apply 返回原始对象,可以继续操作
示例代码
初始化对象属性
data class Person(var name: String = "", var age: Int = 0) fun main() {
val person = Person().apply {
name = "Alice" // 等价于 this.name = "Alice"
age = 30 // 直接访问属性, this 可省略
}
println(person) // 输出: Person(name=Alice, age=30)
}
链式配置多个属性
class Car {
var brand: String = ""
var speed: Int = 0
fun start() { println("$brand 启动,速度: $speed km/h") } }
fun main() {
var car = Car()
.apply { brand = "XiaoMi SU7" }
.apply { speed = 200 }
.apply { start() } // 输出: XiaoMi SU7 启动,速度: 200 km/h
}
替代 Builder 模式
// 传统 Builder 模式 vs apply 简化
class Dialog {
var title: String = ""
var message: String = "" fun show() { println("显示对话框:$title - $message") }
} fun main() {
// 传统方式
val dialog1 = Dialog()
dialog1.title = "提示"
dialog1.message = "欢迎使用 Kotlin"
dialog1.show() // 使用 apply(更简洁)
val dialog2 = Dialog().apply {
title = "提示"
message = "欢迎使用 Kotlin"
show() // 直接调用方法
}
}
处理可空对象
fun configureNullableObject() {
val nullableConfig: String? = null
nullableConfig?.apply {
println("配置非空对象:$this") // 此处不会执行
} ?: println("对象为空") // 输出:对象为空
} fun main() {
configureNullableObject()
}
常见误区
错误:在
apply中返回其他值data class Person (var name: String = "",var age: Int = 0) fun main() {
val person = Person().apply {
name = "Bob"
"这是一个错误示例" // 这行代码无效
}
println(person)
}
正确的做法是: 若需要返回计算结果,应使用
run:data class Person (var name: String = "",var age: Int = 0) fun main() {
val person = Person().run {
name = "Bob"
"姓名: $name"
}
println(person) // 输出: 姓名: Bob
}
高级用法
链式调用多个作用域函数
data class File(var absolutePath: String = "") {
fun readText() {
// ... 读取文件操作
println("读取文件操作")
}
} fun createNewFile() {
// ... 创建文件操作
println("创建文件操作")
}
fun main() {
File("data.txt")
.apply { createNewFile() }
.also { println("文件路径: ${it.absolutePath}") }
.readText()
}
结合
takeIf过滤条件data class Person(var name: String = "", var age: Int = 0)
fun main() {
val validPerson = Person().apply {
name = "Charlie"
age = 25
}.takeIf { it.age >= 18 } // 只保留成年人
println(validPerson) // 输出:Person(name=Charlie, age=25)
}
with:对非空对象执行多个操作
在 Kotlin 中,with 是一个作用域函数,用于对已有对象执行多个操作,它通过将对象作为上下文(this)传递到代码块中,让代码更集中、更易读。以下是 with 的详细讲解
基本语法
val 结果 = with(对象) {
// 在此直接访问对象的属性和方法(this 可省略)
操作1
操作2
...
最后一行作为返回值
}
示例代码
批量操作对象属性
data class User(var name: String = "", var age: Int = 0) fun main() {
val user = User("Alice", 25)
val info = with(user) {
name = "Bob" // 等价于 this.name = "Bob"
age += 5 // 修改属性
"更新后:$name, $age 岁" // 返回字符串
}
println(info) // 输出:更新后:Bob, 30 岁
println(user) // 输出:User(name=Bob, age=30)
}
执行计算并返回结果
class Rectangle(val width: Int, val height: Int) {
fun area() = width * height
} fun main() {
val rect = Rectangle(10, 20)
val result = with(rect) {
val perimeter = 2 * (width + height) // 访问属性
"面积: ${area()}, 周长: $perimeter" // 调用方法,返回字符串
}
println(result) // 输出:面积: 200, 周长: 60
}
处理集合操作
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
val summary = with(numbers) {
val sum = sum()
val avg = average()
"总和: $sum, 平均值: $avg" // 返回统计结果
}
println(summary) // 输出:总和: 15, 平均值: 3.0
}
常见误区
错误:对可空对象直接使用
withdata class Person(var name: String = "", var age: Int = 0) fun main() {
val person: Person? = null
with(person) {
println(name) // 编译报错 Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Person?
}
}
正确做法
data class Person(var name: String = "", var age: Int = 0) fun main() {
val user: Person? = null
with(user ?: return) { // 如果 user 为 null,提前退出
println(name)
}
}
经典应用场景
集中配置对象属性
data class Person(var name: String = "", var age: Int = 0) {
fun sout() {
println("Name: $name, Age: $age")
}
} fun main() {
val person = Person()
with(person) {
name = "NPC"
age = 24
sout() // 输出: Name: NPC, Age: 24
} }
数据转换与计算
fun main() {
val list = listOf(1, 2, 3)
val squaredSum = with(list) {
map { it * it }.sum()
}
println(squaredSum) // 输出: 14
}
简化多步操作
data class File(var fileName: String ="") {
fun createNewFile() {
println("创建文件 $fileName")
}
fun exists(): Boolean {
println("判断 $fileName 文件是否存在")
return false
}
fun readText(): String {
return "读取文件 $fileName"
}
} fun main() {
val file = File("data.txt")
val content = with(file) {
if (!exists()) createNewFile()
readText().uppercase()
}
println(content) // 输出: 读取文件 DATA.TXT
}
also:附加操作(如打印日志)
在 Kotlin 中,also 是一个作用域函数,专注于执行附加操作(如日志记录、验证、调试),同时保留对象本身,并支持链式调用。它不会修改对象,但允许在对象操作流程中插入“副作用”。以下是 also 的详细讲解
基本语法
val 对象 = 原始对象.also {
// 通过 it 访问对象,执行附加操作
// 返回原始对象(无论代码块内做了什么)
}
示例代码
打印日志(调试中间状态)
data class User(var name: String, var age: Int) fun main() {
val user = User("Alice", 25)
.also { println("初始化后:$it") } // 输出:初始化后:User(name=Alice, age=25)
.apply { age += 5 }
.also { println("修改年龄后:$it") } // 输出:修改年龄后:User(name=Alice, age=30) println("最终结果:$user") // 输出:最终结果:User(name=Alice, age=30)
}
数据验证
fun processOrder(order: Order?) {
order?.also {
require(it.amount > 0) { "订单金额必须大于 0" }
requireNotNull(it.customer) { "订单必须有客户信息" }
}?.also {
println("开始处理订单:${it.id}") // 验证通过后执行
}
} data class Order(val id: String, var amount: Double, var customer: String?) fun main() {
val validOrder = Order("123", 100.0, "Alice")
processOrder(validOrder) // 输出:开始处理订单:123 val invalidOrder = Order("456", -50.0, null)
processOrder(invalidOrder) // 抛出 IllegalArgumentException
}
链式调用中插入操作
fun main() {
val list = mutableListOf(1, 2, 3)
.also { it.add(4) } // 添加元素
.also { it.remove(0) } // 删除第一个元素(实际无 0,此操作为演示)
.also { println("当前列表:$it") } // 输出:当前列表:[2, 3, 4] println(list) // 输出:[2, 3, 4]
}
常见误区
错误: 在
also中尝试返回其他值data class User(var name: String, var age: Int) fun main() {
// 错误!also 永远返回原始对象,忽略代码块内的返回值
val user = User("Alice", 25).also {
"无效的返回值" // 这行代码无意义
}
println(user) // 输出:User(name=Alice, age=25)
}
正确做法: 若需返回计算结果,应使用
let或rundata class User(var name: String, var age: Int) fun main() {
val info = User("Alice", 25).let {
"${it.name} 的年龄是 ${it.age}" // 返回字符串
}
println(info) // 输出:Alice 的年龄是 25
}
高级用法
结合
takeIf进行条件过滤data class User(var name: String, var age: Int) fun createUser(name: String?, age: Int): User? {
return name?.let {
User(it, age)
}?.takeIf { it.age >= 18 } // 只保留成年人
?.also { println("用户创建成功:$it") } // 记录日志
} fun main() {
val user = createUser("Bob", 20) // 输出:用户创建成功:User(name=Bob, age=20)
println(user) // 输出:User(name=Bob, age=20)
}
再集合操作中跟踪流程
fun main() {
val numbers = (1..10)
.filter { it % 2 == 0 }
.also { println("过滤后的偶数:$it") } // 输出:过滤后的偶数:[2, 4, 6, 8, 10]
.map { it * it }
.also { println("平方结果:$it") } // 输出:平方结果:[4, 16, 36, 64, 100]
}
核心对比表
| 函数 | 上下文对象引用 | 返回值 | 典型场景 | 空安全支持 |
|---|---|---|---|---|
let |
it |
Lambda结果 | 可空对象处理、数据转换 | 需配合?.(object?.let) |
run |
this |
Lambda结果 | 对象配置 + 返回结果计算、独立作用域 | 需配合?.(object?.run) |
apply |
this |
对象本身 | 对象初始化(链式配置属性) | 需配合?.(object?.apply) |
with |
this |
Lambda结果 | 对已有非空对象批量操作 | 需自行处理空安全 |
also |
it |
对象本身 | 附加操作(日志、验证)、链式调用中的中间操作 | 需配合?.(object?.also) |
上下文对象引用
data class Car(var speed: Int = 0) {
fun accelerate() { speed += 10 }
} fun main() {
val car = Car().apply {
speed = 100 // 直接访问属性(this 可省略)
accelerate() // 直接调用方法
}
println(car) // 输出:Car(speed=110)
}
it(显示引用):let、alsofun main() {
val list = mutableListOf(1, 2, 3)
.also { it.add(4) } // 显式用 it 操作
.let { it.joinToString("-") } // 转换为字符串
println(list) // 输出:1-2-3-4
}
返回对象本身:
apply、alsodata class Button(var text: String = "", var textSize: Float = 0.0f) fun main() {
// apply 返回对象本身,适合链式配置
val button = Button().apply {
text = "Submit"
textSize = 16f
}
// also 返回对象本身,适合插入附加操作
button.also { println("按钮已配置:$it") } // 输出: 按钮已配置:Button(text=Submit, textSize=16.0) }
返回 Lambda 结果:
let、run、alsodata class Car(var speed: Int = 0) {
fun accelerate() { speed += 10 }
} fun main() {
// let 返回转换后的数据
val length = "Kotlin".let { it.length } println(length) // 输出: 6 // run 返回计算结果
val area = Car(200).run { speed * 2 } println(area) // 输出: 400 // with 返回处理后的结果
val info = with(Car()) {
speed = 100
"车速:$speed km/h"
} println(info) // 输出: 车速: 100 km/h
}
需配合
?.:let、run、apply、alsofun main() {
val nullableString: String? = null nullableString?.let {
println(it.length) // 非空时执行
} ?: println("字符串为空") nullableString?.apply {
println(length) // 非空时执行
}
}
需自行处理:
withdata class User(var name: String = "", var age: Int = 0) fun main() {
val user: User? = User()
user?.let {
with(it) { // 确保非空后使用 with
name = "Alice"
age = 30
}
}
println(user) // 输出: User(name=Alice, age=30)
}
选择指南
- 是否需要返回对象本身?
- 是 →
apply或also(根据是否需要显式it)。 - 否 →
let、run、with(根据是否需要隐式this)。
- 是 →
- 是否需要处理可空对象?
- 是 →
?.let、?.run、?.apply、?.also。 - 否 →
with或直接使用其他函数。
- 是 →
- 是否需要链式调用中的中间操作?
- 是 →
also(如记录日志)。 - 否 → 根据返回值需求选择。
- 是 →
混合使用示例
// 链式调用:初始化、验证、转换
data class Product(var name: String = "", var price: Double = 0.0)
fun main() {
val productInfo = Product()
.apply {
name = "手机"
price = 2999.0
}
.also {
require(it.price > 0) { "价格必须大于 0" }
println("商品已初始化:$it")
}
.let {
"${it.name} 价格:${it.price} 元" // 返回字符串
}
println(productInfo) // 输出:手机 价格:2999.0 元
}
学习Kotlin语法(四)的更多相关文章
- Python学习系列(四)Python 入门语法规则2
Python学习系列(四)Python 入门语法规则2 2017-4-3 09:18:04 编码和解码 Unicode.gbk,utf8之间的关系 2.对于py2.7, 如果utf8>gbk, ...
- 我的MYSQL学习心得(四) 数据类型
我的MYSQL学习心得(四) 数据类型 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(五) 运 ...
- ios -- 教你如何轻松学习Swift语法(一)
目前随着公司开发模式的变更,swift也显得越发重要,相对来说,swift语言更加简洁,严谨.但对于我来说,感觉swift细节的处理很繁琐,可能是还没适应的缘故吧.基本每写一句代码,都要对变量的数据类 ...
- X-Cart 学习笔记(四)常见操作
目录 X-Cart 学习笔记(一)了解和安装X-Cart X-Cart 学习笔记(二)X-Cart框架1 X-Cart 学习笔记(三)X-Cart框架2 X-Cart 学习笔记(四)常见操作 五.常见 ...
- WCF学习心得----(四)服务承载
WCF学习心得----(四)服务承载 这一章节花费了好长的时间才整理个大概,主要原因是初次接触这个东西,在做练习实践的过程中,遇到了很多的问题,有些问题到目前还没有得以解决.所以在这一章节中,有一个承 ...
- 程序员带你学习安卓开发,十天快速入-对比C#学习java语法
关注今日头条-做全栈攻城狮,学代码也要读书,爱全栈,更爱生活.提供程序员技术及生活指导干货. 如果你真想学习,请评论学过的每篇文章,记录学习的痕迹. 请把所有教程文章中所提及的代码,最少敲写三遍,达到 ...
- VSTO学习笔记(四)从SharePoint 2010中下载文件
原文:VSTO学习笔记(四)从SharePoint 2010中下载文件 上一次我们开发了一个简单的64位COM加载项,虽然功能很简单,但是包括了开发一个64位COM加载项的大部分过程.本次我们来给CO ...
- 浅谈Kotlin(四):控制流
浅谈Kotlin(一):简介及Android Studio中配置 浅谈Kotlin(二):基本类型.基本语法.代码风格 浅谈Kotlin(三):类 浅谈Kotlin(四):控制流 本篇介绍Kotlin ...
- 学习HTML 第四节.插入图像
学习HTML 第四节.插入图像 全是文字的网页太枯燥了吧,我们来搞个图片上去! <!DOCTYPE html><html><head><meta charse ...
- 推荐两份学习 Kotlin 和机器学习的资料
最近 Kotlin 和人工智能比较火,有不少同学留言问我怎么学习 Kotlin,怎么学习机器学习,今天就给大家推荐两份不错的学习资料. 1. Kotlin 学习资料其实,在我看来最好的学习资料就是 K ...
随机推荐
- 10.3 - AM - 模拟赛 总结
复盘 T1 很水,一道异或求和,但是某两位仁兄因没打括号而死. T2 很水,一道字符串处理,但是我和某位仁兄因没特判而死(虽然没有 hack 掉我,所以我理论上还是满分). T3 不水,看了很久,没想 ...
- python读取excel的文件
1.安装依赖包,并且导入 pip install xlrd import xlrd 2.打开文件 path = r"C:\Users\xiao\Desktop\服务体系.xls" ...
- colab 使用技巧
无法进入目录 import os path = "/content/TaBERT/" os.chdir(path) print(os.getcwd()) 无法执行conda !pi ...
- AGC018
AGC018 B 题目大意 举办一场运动会,有 \(N\) 人,\(M\) 个项目,每个人所有项目都有一个排名,会选择参加排名最高且开设的项目,现在要开设若干项目使得人数最多的项目人数尽可能小,求这个 ...
- 1.某道翻译js逆向sign值
首先找到这个请求接口 这个接口就是我们请求翻译的接口 发现有个sign值,这就是我们需要逆向的值 再看看这个接口的响应 可以发现这个响应是被加密的,我们还需要去逆向解密这个被加密的响应,这篇就单纯讲一 ...
- flutter真机调试出现flutter Launching 'app' on No Devices.
1. flutter真机调试出现flutter Launching 'app' on No Devices. flutter Launching 'app' on No Devices. 我的是华为手 ...
- 内容分发网络 CDN 概述
本文分享自天翼云开发者社区<内容分发网络 CDN 概述>,作者:Jerry CDN(Content Delivery Network)是一种分布式网络架构,旨在提供高效.可靠地将内容传送给 ...
- 安装和配置CentOS9
安装和配置CentOS9 一.下载CentOS9镜像文件 1.访问官网:首先,你需要访问CentOS的官网或阿里云镜像网站 2.选择版本:在官网上,选择CentOS9的64位操作系统版本进行下载. 3 ...
- 用 just 简化项目命令管理
在软件开发过程中,高效管理项目命令是提升开发效率的关键, 它们可以帮助我们自动化重复的任务,简化项目管理流程,提高效率. 今天,我们来介绍一个名为 just 的任务运行器. 它由 Casey 发起,用 ...
- CAD内核的奥秘 | 工业软件发展史 (转)
CAD内核的奥秘 | 工业软件发展史 (声明:此文非本人原著,仅供交流,如果侵犯到原作者权利,立即删除) 如果一个产业要寻根,就会发现一个万千世界,最后会聚焦到一个点上. "一沙一世界&qu ...