一、作用域函数详解

1. apply:对调用对象进行配置或操作,并返回该对象本身。

  • 接收者引用this(可省略,直接调用接收者成员)
  • 返回值:接收者对象本身(T
  • 核心用途:对对象进行初始化或配置,返回配置后的对象
  • null 安全:不支持(接收者需非 null)
  • 作用域:接收者作用域(this 指向接收者)
open val sessionHandler: Handler by lazy {Handler(HandlerThread(tag).apply { this.start() } // 启动线程并返回 HandlerThread 实例.looper // 获取线程的 Looper)
}

        apply用于启动HandlerThread线程并返回该线程实例,从而让后续代码能顺利获取其looper来初始化Handler

        具体来看,HandlerThread(tag).apply { this.start() }这部分代码中,apply的 lambda 表达式里通过this.start()调用了HandlerThreadstart方法来启动线程,由于apply会返回调用它的对象(即HandlerThread实例),所以后续能继续通过.looper获取该线程的消息循环器,进而将其作为参数传递给Handler完成初始化。

        这种写法的优势在于通过链式调用让代码更紧凑,避免了额外的变量声明,同时利用apply的作用域特性让this直接指向HandlerThread实例,使代码逻辑更清晰简洁。 

2. let:其核心作用是对非空对象执行操作,并返回 lambda 表达式的结果。

  • 接收者引用it(隐式参数,作为 lambda 的唯一参数)
  • 返回值:lambda 的执行结果(任意类型)
  • 核心用途:处理 null 值,限定变量作用域,返回新计算结果
  • null 安全:支持(配合安全调用符 ?.
  • 作用域:独立作用域(it 仅在 lambda 内可见)
extras?.let {// 当 extras 不为空时执行此代码块val metadataList = it.getParcelableArrayList<MediaBrowserCompat.MediaItem>(MEDIAITEM_LIST_KEY) ?: emptyList()var itemToPlay = it.getParcelable<MediaBrowserCompat.MediaItem>(MEDIAITEM_KEY)// 当未获取到媒体项时,从 mediaId 构建默认媒体项if (metadataList.isEmpty() && itemToPlay == null && mediaId != null) {// ... 构建 MediaItem 的逻辑 ...}// 准备播放列表preparePlaylist(mediaId ?: "", metadataList, itemToPlay, true, it)
}

        extras?.let { ... } 的主要作用是确保 extras 不为空时执行后续逻辑,同时避免了重复的空值检查。

        具体来说,let 在这里的工作方式如下:首先,通过安全调用操作符 ?.,代码会先检查 extras 是否为 null,只有当 extras 不为空时,才会执行 let 中的 lambda 表达式,这有效避免了 NullPointerException

        其次,在 lambda 表达式内部,it 代表非空的 extras 对象,这使得代码更简洁,无需重复引用 extras。例如,it.getParcelableArrayList(...) 等价于 extras.getParcelableArrayList(...),但无需重复检查 extras 是否为空。

        此外,let 还能控制变量的作用域,在 let 内部定义的变量(如 metadataListitemToPlay)仅在该作用域内可见,避免了变量泄漏。如果不使用 let,代码需要显式检查 extras 是否为空,会更冗长,而 let 让代码更紧凑,同时保持空安全。这种写法符合 Kotlin 的空安全设计理念,使代码更简洁、更安全。 

    3. also:对调用对象执行额外操作后返回该对象本身

    • 接收者引用it(隐式参数,作为 lambda 的唯一参数)
    • 返回值:接收者对象本身(T,同 apply
    • 核心用途:执行副作用操作(如日志、赋值),保持对象链式调用
    • null 安全:不支持(接收者需非 null)
    • 作用域:独立作用域(it 仅在 lambda 内可见)
    override fun setShuffleMode(shuffleMode: Int) {synchronized(mediaSession) {mediaSession.setShuffleMode(shuffleMode)}// 将系统 shuffleMode 转换为内部 PlayMode 枚举PlayMode.LOOP.shuffle(shuffleMode)?.also { playMode ->// 执行副作用操作(更新状态和发送事件)setPlayMode(playMode)           // 更新内部播放模式sendNextModeEvent(playMode)     // 通知模式变更}
    }

            PlayMode.LOOP.shuffle(shuffleMode)?.also { ... } 的主要作用是在将系统随机模式(shuffleMode)转换为应用内部的播放模式(PlayMode)后,执行副作用操作(如更新状态和发送事件),同时保持链式调用的流畅性。

            具体来说,also 在这里的工作方式如下:首先,PlayMode.LOOP.shuffle(shuffleMode) 尝试将传入的系统随机模式转换为对应的 PlayMode 枚举值,若转换成功则返回非空值,否则返回 null。通过安全调用操作符 ?.,代码确保仅在转换结果非空时执行 also 内的 lambda 表达式,避免了空指针异常。

            在 lambda 表达式内部,setPlayMode(playMode) 更新应用的内部播放模式状态,而 sendNextModeEvent(playMode) 则发送模式变更事件通知,这两个操作均属于对 playMode 对象的副作用处理。

            最后,also 会返回原始的转换结果(即 playMode),尽管在这段代码中没有后续调用,但这种设计保持了 API 的灵活性。如果不使用 also,代码需要额外的变量声明和条件判断,会更冗长,而 also 让代码更紧凑,同时保持空安全,符合 Kotlin 的函数式编程风格。 

    4. run:接收者作用域的 “全能选手”

    • 接收者引用this(可省略,直接调用接收者成员)
    • 返回值:lambda 的执行结果(任意类型)
    • 核心用途:在接收者作用域内执行代码块,混合调用成员方法和外部函数
    • null 安全:不支持(需手动校验接收者 null)
    • 作用域:接收者作用域(优先访问接收者成员)
    代码示例:
    // 计算文件内容长度
    val file = File("data.txt")
    val contentLength = file.run { if (exists()) readText().length else 0
    }// 链式函数调用
    "Android".run {toUpperCase()       // 调用接收者方法
    }.run { "$this Kotlin"      // 处理中间结果
    }.run(::println)      // 调用外部函数(打印结果)
    最佳实践:
    • 成员访问:简化接收者成员调用(如 view.run { setText("OK") }
    • 混合逻辑:同时使用接收者方法(length)和外部函数(println

    5. withrun 的参数化变体

    • 接收者引用:参数传入(非扩展函数,直接在 lambda 中使用接收者)
    • 返回值:lambda 的执行结果(同 run
    • 核心用途:以非扩展函数形式使用 run,显式传入接收者
    • null 安全:不支持(需手动校验接收者 null)
    • 作用域:接收者作用域(同 run
    代码示例:
    // 显式传入接收者(非扩展函数调用)
    val result = with(ArrayList<String>()) {add("A")add("B")size  // 返回 lambda 结果
    }// 数学计算场景
    val point = Point(3, 4)
    val distance = with(point) { sqrt(x*x + y*y)  // 直接访问 x/y 属性(假设 Point 有 x/y 成员)
    }
    最佳实践:
    • 多对象操作:当接收者不是调用对象时(如 with(list, ::process)
    • 替代 run:习惯参数化调用时使用(与 run 功能完全一致)

    三、对比表格:快速选择指南

    函数接收者引用返回值核心用途null 安全作用域类型典型场景
    applythis接收者对象对象配置接收者作用域初始化对象、设置属性
    letitlambda 结果空安全处理、返回新值是(?.独立作用域处理 nullable 对象、限定作用域
    runthislambda 结果成员操作 + 函数调用接收者作用域混合调用对象方法和外部函数
    with参数传入lambda 结果非扩展函数形式的 run接收者作用域显式传入接收者、多对象操作
    alsoit接收者对象链式副作用(日志、赋值)独立作用域保持对象链式调用,执行附加操作

    四、最佳实践与避坑指南

    1. 对象配置首选 apply

    当需要对对象进行初始化或设置属性时,apply 能避免临时变量,使代码更流畅:

    // 推荐:直接返回配置后的对象
    val button = Button().apply {text = "提交"setOnClickListener { ... }
    }

    2. null 安全首选 let

    处理可为 null 的对象时,let 配合 ?. 是最佳选择:

    // 避免 NPE:安全调用 + let
    networkResponse?.let { handle(it) }  

    3. 成员访问首选 run/with

    当需要频繁调用接收者成员(如 file.readText())时,run 或 with 更简洁:

    // 简化成员访问
    file.run {if (exists()) readText() else ""
    }

    4. 链式副作用首选 also

    执行日志记录、变量赋值等非核心操作时,also 能保持对象链式调用:

    // 链式流程中插入日志
    downloadFile().also { logDownload(it) }.also { saveToCache(it) }

    5. 避免混淆返回值

    • apply/also 返回接收者对象,适合继续配置(如 .apply(...).also(...)
    • let/run 返回 lambda 结果,适合生成新值(如 val result = obj.let { ... }

    五、总结:选择的艺术

    Kotlin 的作用域函数是函数式编程与面向对象的完美结合,掌握它们的关键在于:

    • 明确目标:配置对象用 apply,处理 null 用 let,混合逻辑用 run
    • 关注返回值:需保持对象链式调用选 apply/also,需计算结果选 let/run
    • 代码风格:习惯扩展函数用 apply/let/run,习惯参数化调用用 with

           这些函数并非互斥,而是互补。例如,apply 配合 also 可实现 “配置 + 日志” 的复合操作,let 配合 run 可处理 null 值并执行复杂逻辑。熟练运用这组工具,能让代码兼具简洁性与可读性,真正体现 Kotlin 的优雅与高效。

    本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
    如若转载,请注明出处:http://www.pswp.cn/bicheng/86444.shtml
    繁体地址,请注明出处:http://hk.pswp.cn/bicheng/86444.shtml
    英文地址,请注明出处:http://en.pswp.cn/bicheng/86444.shtml

    如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

    相关文章

    Spring Boot监视器:应用监控终极指南

    Spring Boot 监视器详解 Spring Boot 监视器(Monitor)是用于监控和管理 Spring Boot 应用程序运行状态的核心组件,主要通过 Spring Boot Actuator 和 Spring Boot Admin 两大工具实现。 一、核心监视器组件 1. Spring Boot Actuator 功能定位:提供应用程序内部运行状态的原…

    SpringBoot 中 @Transactional 的使用

    SpringBoot 中 Transactional 的使用 一、Transactional 的基本使用二、Transactional 的核心属性三、使用避坑&#xff08;失效场景&#xff09;3.1 自调用问题3.2 异常处理不当3.3 类未被 Spring 管理3.4 异步方法内使用失效 四、工作实践4.1 事务提交之后执行一些操作4.2 事…

    6.26_JAVA_微服务_Elasticsearch

    1、ES文档中keyword意思是&#xff1a;字符串&#xff0c;但不需要分词 2、ES细节CreateIndexRequest request new CreateIndexRequest("items");会让你导包&#xff0c;会有两个选择&#xff1a; import org.elasticsearch.action.admin.indices.create.CreateInd…

    Java 大视界 -- 基于 Java 的大数据可视化在智慧城市能源消耗动态监测与优化决策中的应用(324)

    Java 大视界 -- 基于 Java 的大数据可视化在智慧城市能源消耗动态监测与优化决策中的应用&#xff08;324&#xff09; 引言&#xff1a;正文&#xff1a;一、Java 驱动的能源数据采集与预处理基建1.1 多源异构数据合规接入层&#xff08;ISO 50001IEC 61850 双标准适配&#x…

    C++ 快速回顾(二)

    C 快速回顾&#xff08;二&#xff09; 前言一、友元类二、友元函数三、深浅拷贝浅拷贝深拷贝 前言 用于快速回顾之前遗漏或者补充C知识 一、友元类 友元的优点是可以快速的轻松的访问的原本由于私有保护的字段和函数&#xff0c;同时这也是它的缺点这样破坏了原本封装性。 …

    ldl-DeserializationViewer一款强大的序列化数据可视化工具

    ldl-DeserializationViewer 一款强大的序列化数据可视化工具&#xff0c;能够将Java序列化的缓存数据转换为可读的JSON格式&#xff0c;无需原始DTO类定义。 A powerful visualization tool for serialized data that converts Java serialized cache data to readable JSON f…

    NetworkSecurity SIG成立,助力国产操作系统安全生态发展

    近期&#xff0c;ZeroOnes实验室团队成员在OpenAtom openKylin&#xff08;简称“openKylin”&#xff09;社区发起成立NetworkSecurity SIG&#xff0c;负责基于openKylin系统开展网络安全工具的研发与适配&#xff0c;助力国产操作系统安全生态发展。 ZeroOnes实验室专注于网…

    回归任务与分类任务的区别

    回归任务&#xff08;Regression&#xff09;与分类任务&#xff08;Classification&#xff09;是机器学习的两大核心任务类型&#xff0c;其根本区别在于输出变量的性质和任务目标。以下是系统性对比&#xff1a; 1. 本质区别&#xff1a;输出变量类型 任务类型输出&#xf…

    Webshell工具的流量特征分析(菜刀,蚁剑,冰蝎,哥斯拉)

    Webshell工具的流量特征分析&#xff08;菜刀&#xff0c;蚁剑&#xff0c;冰蝎&#xff0c;哥斯拉&#xff09; 0x00 前言 使用各种的shell工具获取到目标权限&#xff0c;即可进行数据操作&#xff0c;今天来简要分析一下目前常使用的各类shell管理工具的流量特诊&#xff…

    【linux】全志Tina配置swupdate工具进行分区打包

    一、文件路径 1、描述文件&#xff1a; .\build\swupdate\sw-description-ab 2、镜像打包文件&#xff1a; .\build\swupdate\sw-subimgs-ab.cfg 二、文件作用 1、sw-description-ab 用于描述版本信息和ab区中要打包的分区信息以及挂载点。 2、sw-subimgs-ab.cfg 用于…

    MicroPython网络编程:AP模式与STA模式详解

    文章目录 1. MicroPython网络模块概述2. 热点AP模式详解2.1 什么是AP模式?2.2 AP模式特点2.3 AP模式设置代码2.4 AP模式适用场景3. 客户端STA模式详解3.1 什么是STA模式?3.2 STA模式特点3.3 STA模式设置代码3.4 STA模式适用场景4. AP与STA模式对比分析5. 实际应用场景与选择建…

    Ubuntu网络数据包发送工具大全

    在Ubuntu系统中&#xff0c;有多种工具可以用于发送网络数据包&#xff0c;包括UDP、TCP、ICMP等协议。以下是一些常用的工具及其简要介绍&#xff1a; 1. Packet Sender 功能&#xff1a;支持发送和接收TCP、UDP和SSL数据包&#xff0c;提供图形界面和命令行工具。安装&…

    小学期前端三件套学习(更新中)

    第一阶段 HTML 基础结构 <!DOCTYPE html> <html><head><title>页面标题</title></head><body>页面内容</body> </html>常用内容标签 文本类标签 • <h1>~<h6>&#xff1a;标题&#xff08;h1 每个页面建…

    高斯混合模型(Gaussian Mixture Model, GMM)

    高斯混合模型&#xff08;Gaussian Mixture Model, GMM&#xff09; 是一种 概率模型&#xff0c;用于表示数据点由多个高斯分布&#xff08;Gaussian Distribution&#xff09;混合生成的过程。它广泛应用于 聚类分析、密度估计、图像分割、语音识别 等领域&#xff0c;尤其适…

    MCP Client 开发 -32000 报错

    在开发 MCP Client 的过程中&#xff0c;发生了 -32000 报错&#xff0c;源码如下&#xff1a; import json from typing import Optional from contextlib import AsyncExitStackfrom openai import OpenAIfrom mcp import ClientSession, StdioServerParameters from mcp.cl…

    使用zabbix监控Nginx服务的配置方法

    准备 要监控Nginx的服务状态&#xff0c;首先需要安装nginx的status模块&#xff1a;ngx_http_stub_status_module 首先 查看Nginx是否有安装该模块&#xff1a;--with-http_stub_status_module nginx -V 如果没有安装的话&#xff0c;安装方法可以参照&#xff1a;Nginx新…

    简易服务器(TCP)

    1.简单介绍以及项目技术和开发环境 本文将通过epoll完成对客户端请求的处理&#xff0c;通过多线程完成对客户端发送数据的处理&#xff0c;并提交到远端mysql 需要的使用到的一些技术有&#xff1a;socket网络套接字编程、IO多路转接的epoll、多线程&#xff08;包括互斥锁和条…

    总结前端三年 理想滚烫与现实的冰冷碰撞

    大家好&#xff0c;我是500佰&#xff0c;技术宅男 目前正在前往独立开发路线&#xff0c;我会在这里分享关于编程技术、独立开发、技术资讯以及编程感悟等内容 6月3日的一篇《一个普通人的30岁 他经历了什么》介绍一篇自己的碎碎念、即回顾自己以前的成长经历&#xff0c;那么…

    微服务网关/nacos/feign总结

    现在学习到的组件 1.nacos&#xff1a;注册中心&#xff0c;用于微服务之间交流的第三方管家&#xff0c;与生产者建立心跳契约对其监听&#xff0c;注册中心维护一张生产者的活跃表&#xff0c;会将活跃表实时更新并推送给消费者。 2.feign&#xff1a;nacos只是对生产者进行…

    WebSocket 协议详解

    WebSocket 协议详解 1. WebSocket 协议的帧数据详解 1.1 帧结构 0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1--------------------------------------------------------|F|R|R|R| opco…