Kotlin开发(七):对象表达式、对象声明和委托的奥秘
Kotlin 让代码更优雅!
每个程序员都希望写出优雅高效的代码,但现实往往不尽人意。对象表达式、对象声明和 Kotlin 委托正是为了解决代码中的复杂性而诞生的。为什么选择这个主题?因为它不仅是 Kotlin 语言的亮点之一,还能极大地提高代码的复用性和可读性!无论你是 Kotlin 初学者还是老司机,这篇文章都能让你对这些概念有全新的理解。带着好奇心和一颗想偷懒的心,一起探讨如何用 Kotlin 玩转“对象三兄弟”吧!

一、对象的世界,从复杂到简单
在 Java 世界里,对象无处不在,但代码常常被重复使用的逻辑“绑架”,导致臃肿难维护。Kotlin 提供了三种“杀手锏”来优雅解决这个问题:
- 对象表达式:替代匿名内部类,写起来更短更爽!
- 对象声明:当你需要一个全局唯一的对象时,它就是你的好帮手。
- Kotlin 委托:一个字,懒!两个字,复用!用现成的功能替代手写代码,效率倍增。
它们的出现不仅提升了开发体验,也成为 Kotlin 的杀手级功能之一!
Kotlin 对象表达式与对象声明详解
Kotlin 提供了对象表达式和对象声明来方便开发,允许在不定义新子类的情况下对类或接口进行轻微的修改。这在实现单例模式、匿名内部类以及属性委托时非常实用。以下是关于它们的详细解析。
对象表达式
对象表达式用于创建匿名对象。通过它可以快速定义一个对象,同时可以继承基类或实现接口。主要用于需要即时实现特定行为的场景,如事件监听器。
1. 创建匿名内部类对象
对象表达式的典型用法是创建匿名内部类:
window.addMouseListener(object : MouseAdapter() {override fun mouseClicked(e: MouseEvent) {// 实现点击事件}override fun mouseEntered(e: MouseEvent) {// 实现鼠标进入事件}
})
2. 继承基类和实现接口
对象可以同时继承基类和实现接口:
open class A(x: Int) {open val y: Int = x
}interface Bval ab: A = object : A(1), B {override val y = 15
}
注意:
- 如果基类有构造函数,必须为其传递参数。
- 超类型(如基类和接口)用逗号分隔。
3. 匿名对象作为局部变量
对象表达式可以直接生成一个对象,避免定义类:
fun main() {val site = object {var name: String = "云起教程"var url: String = "https://blog.csdn.net/bryant_liu24"}println(site.name)println(site.url)
}
4. 匿名对象的作用域
匿名对象的类型在不同作用域下表现不同:
- 在局部作用域或私有函数中,匿名对象类型保留成员。
- 在公共函数或属性中,返回的类型会被擦除为
Any。
class C {private fun privateFoo() = object {val x: String = "私有对象"}fun publicFoo() = object {val x: String = "公共对象"}fun test() {val privateX = privateFoo().x // 正常访问// val publicX = publicFoo().x // 编译错误:未能解析 x}
}
对象声明
Kotlin 使用 object 关键字声明对象。对象声明用于创建单例,并且在第一次访问时延迟初始化。
1. 单例模式
通过对象声明,可以轻松实现单例模式:
object DataProviderManager {fun registerDataProvider(provider: String) {println("注册数据提供者: $provider")}val allDataProviders: List<String> = listOf("Provider1", "Provider2")
}
使用方式:
DataProviderManager.registerDataProvider("Provider3")
println(DataProviderManager.allDataProviders)
2. 对象声明与类的关系
对象声明可以继承类或实现接口:
object DefaultListener : MouseAdapter() {override fun mouseClicked(e: MouseEvent) {println("鼠标点击事件")}
}
3. 对象声明的限制
如果对象声明在类内部,无法通过外部类的实例访问,只能通过外部类的类名访问:
class Site {object DeskTop {var url = "www.runoob.com"}
}// 访问方式:
Site.DeskTop.url
伴生对象
Kotlin 提供 companion object 来声明类的关联对象。伴生对象中的成员可以直接通过外部类访问。
1. 定义伴生对象
class MyClass {companion object Factory {fun create(): MyClass = MyClass()}
}// 调用伴生对象方法
val instance = MyClass.create()
如果省略伴生对象的名字,可以使用默认名称 Companion:
class MyClass {companion object
}val companionInstance = MyClass.Companion
2. 伴生对象实现接口
伴生对象在运行时是类的实例,因此可以实现接口:
interface Factory<T> {fun create(): T
}class MyClass {companion object : Factory<MyClass> {override fun create(): MyClass = MyClass()}
}
对象表达式与对象声明的区别
| 特性 | 对象表达式 | 对象声明 |
|---|---|---|
| 初始化时机 | 在使用时立即初始化 | 第一次访问时延迟初始化 |
| 是否有名称 | 没有名称,通常是匿名对象 | 有名称,通常是单例 |
| 可见性 | 仅限于作用域 | 全局可见 |
标准库中的委托
Kotlin 标准库提供了一些委托工具,例如 lazy、observable 和 map。
1. 延迟属性 (lazy)
延迟初始化,第一次访问时计算值。
val lazyValue: String by lazy {println("计算 lazyValue")"Hello, Lazy"
}fun main() {println(lazyValue) // 输出:计算 lazyValue\nHello, Lazyprintln(lazyValue) // 输出:Hello, Lazy
}
2. 可观察属性 (observable)
在属性值更改时触发回调。
import kotlin.properties.Delegatesclass User {var name: String by Delegates.observable("初始值") { prop, old, new ->println("属性变化:$old -> $new")}
}fun main() {val user = User()user.name = "Alice"user.name = "Bob"
}
3. 使用 Map 存储属性
将属性值存储在映射中。
class Site(val map: Map<String, Any?>) {val name: String by mapval url: String by map
}fun main() {val site = Site(mapOf("name" to "菜鸟教程", "url" to "www.runoob.com"))println(site.name)println(site.url)
}
Kotlin 的对象表达式、对象声明以及委托机制为开发者提供了灵活且强大的工具。它们让代码更加简洁、可读性更高,同时减少了样板代码的书写。在实际项目中,这些特性可以用于实现单例模式、事件监听、动态属性处理等场景,大大提升开发效率。
二、对象三兄弟大揭秘
1. 对象表达式
快速创建匿名对象的语法,类似 Java 的匿名类,但更简洁:
val anonymousObject = object : Runnable {override fun run() {println("Hello from Kotlin!")}
}
anonymousObject.run()
2. 对象声明
适合单例模式,只需 object 关键字就搞定:
object Singleton {fun showMessage() {println("I am a Singleton!")}
}
Singleton.showMessage()
3. Kotlin 委托
使用 by 关键字,将接口的实现委托给现成的对象:
interface Printer {fun printMessage()
}class DefaultPrinter : Printer {override fun printMessage() = println("Default Printer")
}class CustomPrinter(printer: Printer) : Printer by printerval printer = CustomPrinter(DefaultPrinter())
printer.printMessage()
三、项目案例解析
好的,以下是关于 Kotlin 对象表达式、对象声明和委托的项目实战案例解析。我们将结合一个实际应用场景,逐步展示如何使用这些特性来构建一个模块化、高可维护性的项目。
假设我们需要构建一个任务管理应用,具备以下功能:
- 支持创建任务、查看任务列表。
- 允许任务分组(例如:工作、生活、学习)。
- 支持任务状态跟踪(待办、进行中、已完成)。
- 可通过委托实现配置管理(比如加载用户偏好设置)。
1. 使用对象表达式实现动态监听器
为了动态处理任务状态的变化,我们可以用对象表达式实现匿名类。
示例代码
class Task(val name: String, var status: String)fun main() {val task = Task("学习 Kotlin", "待办")// 使用对象表达式动态监听任务状态变化val statusChangeListener = object {fun onStatusChange(newStatus: String) {println("任务 [${task.name}] 状态更新为:$newStatus")task.status = newStatus}}statusChangeListener.onStatusChange("进行中")statusChangeListener.onStatusChange("已完成")
}
输出结果
任务 [学习 Kotlin] 状态更新为:进行中
任务 [学习 Kotlin] 状态更新为:已完成
解析
对象表达式创建了一个匿名对象,直接将 onStatusChange 方法绑定到任务的状态更新逻辑上,避免了单独创建类的麻烦。
2. 使用对象声明实现单例管理器
我们需要一个全局唯一的任务管理器来管理任务数据,可以通过对象声明来实现。
示例代码
object TaskManager {private val tasks = mutableListOf<Task>()fun addTask(task: Task) {tasks.add(task)println("添加任务:${task.name}")}fun listTasks() {println("当前任务列表:")tasks.forEachIndexed { index, task ->println("${index + 1}. ${task.name} - 状态:${task.status}")}}
}fun main() {TaskManager.addTask(Task("学习 Kotlin", "待办"))TaskManager.addTask(Task("完成项目文档", "进行中"))TaskManager.listTasks()
}
输出结果
添加任务:学习 Kotlin
添加任务:完成项目文档
当前任务列表:
1. 学习 Kotlin - 状态:待办
2. 完成项目文档 - 状态:进行中
解析
通过 object 定义了一个全局单例对象 TaskManager,确保任务管理器在应用中唯一,并可以通过对象名称直接调用方法。
3. 使用委托模式处理分组逻辑
假设任务可以按分组管理,我们通过类委托实现任务分组逻辑的分离。
示例代码
interface GroupManager {fun addToGroup(groupName: String, task: Task)fun listGroupTasks(groupName: String)
}class DefaultGroupManager : GroupManager {private val groups = mutableMapOf<String, MutableList<Task>>()override fun addToGroup(groupName: String, task: Task) {groups.getOrPut(groupName) { mutableListOf() }.add(task)println("任务 [${task.name}] 已添加到分组 [$groupName]")}override fun listGroupTasks(groupName: String) {println("分组 [$groupName] 的任务列表:")groups[groupName]?.forEach { task ->println("任务:${task.name} - 状态:${task.status}")} ?: println("暂无任务")}
}class GroupedTaskManager(private val groupManager: GroupManager) : GroupManager by groupManagerfun main() {val groupManager = GroupedTaskManager(DefaultGroupManager())val task1 = Task("写博客", "待办")val task2 = Task("运动", "进行中")groupManager.addToGroup("生活", task1)groupManager.addToGroup("健康", task2)groupManager.listGroupTasks("生活")groupManager.listGroupTasks("健康")
}
输出结果
任务 [写博客] 已添加到分组 [生活]
任务 [运动] 已添加到分组 [健康]
分组 [生活] 的任务列表:
任务:写博客 - 状态:待办
分组 [健康] 的任务列表:
任务:运动 - 状态:进行中
解析
通过 by 关键字,GroupedTaskManager 委托了 DefaultGroupManager 的逻辑,从而避免了重复实现接口的代码。
4. 使用属性委托管理任务配置
假设需要加载和保存用户的任务偏好设置,我们可以通过属性委托实现统一管理。
示例代码
import kotlin.properties.Delegatesclass UserPreferences {var theme: String by Delegates.observable("默认主题") { _, old, new ->println("主题设置从 [$old] 更新为 [$new]")}var notificationsEnabled: Boolean by Delegates.notNull<Boolean>()
}fun main() {val preferences = UserPreferences()preferences.theme = "暗黑主题"preferences.notificationsEnabled = trueprintln("通知开关:${preferences.notificationsEnabled}")
}
输出结果
主题设置从 [默认主题] 更新为 [暗黑主题]
通知开关:true
解析
- 使用
Delegates.observable监听主题更新,动态响应用户设置的变化。 - 使用
Delegates.notNull确保通知开关在首次访问前被正确初始化。
5. 综合项目架构
将以上所有功能集成到一个任务管理应用中。
主文件
fun main() {val taskManager = TaskManagerval groupManager = GroupedTaskManager(DefaultGroupManager())val preferences = UserPreferences()// 设置用户偏好preferences.theme = "暗黑主题"preferences.notificationsEnabled = true// 添加任务val task1 = Task("学习 Kotlin", "待办")val task2 = Task("写博客", "进行中")taskManager.addTask(task1)taskManager.addTask(task2)// 分组管理groupManager.addToGroup("学习", task1)groupManager.addToGroup("工作", task2)// 显示任务列表taskManager.listTasks()groupManager.listGroupTasks("学习")groupManager.listGroupTasks("工作")
}
总结
通过 Kotlin 的对象表达式、对象声明和委托功能,我们实现了一个模块化的任务管理系统。使用这些特性不仅简化了代码逻辑,还提高了可维护性和扩展性。在实际项目开发中,这些技术可以灵活应用到不同场景中。
四、技术难点及解决方案
-
问题:对象表达式的作用域限制
- 解决:将对象作为变量存储在函数外部,扩展其生命周期。
-
问题:委托性能问题
- 解决:避免复杂逻辑嵌套,保持委托功能单一化。
Kotlin 的对象机制将继续演化,特别是在多平台开发(KMM)中具有巨大潜力。同时,结合 Jetpack Compose 的动态 UI 配置,对象和委托会成为更强大的生产力工具。
五、总结与收获
Kotlin 的对象表达式、对象声明和委托为开发者提供了简洁优雅的解决方案。从实践中我们可以发现,这些工具不仅能优化代码,还能让开发变得更高效。希望大家能尝试在自己的项目中使用这些技巧,并感受到 Kotlin 带来的开发乐趣!
欢迎关注 GongZhongHao,码农的乌托邦,程序员的精神家园!
相关文章:
Kotlin开发(七):对象表达式、对象声明和委托的奥秘
Kotlin 让代码更优雅! 每个程序员都希望写出优雅高效的代码,但现实往往不尽人意。对象表达式、对象声明和 Kotlin 委托正是为了解决代码中的复杂性而诞生的。为什么选择这个主题?因为它不仅是 Kotlin 语言的亮点之一,还能极大地提…...
数据库、数据仓库、数据湖有什么不同
数据库、数据仓库和数据湖是三种不同的数据存储和管理技术,它们在用途、设计目标、数据处理方式以及适用场景上存在显著差异。以下将从多个角度详细说明它们之间的区别: 1. 数据结构与存储方式 数据库: 数据库主要用于存储结构化的数据&…...
【2024年华为OD机试】 (B卷,100分)- 字符串摘要(JavaScriptJava PythonC/C++)
一、问题描述 题目描述 给定一个字符串的摘要算法,请输出给定字符串的摘要值。具体步骤如下: 去除字符串中非字母的符号:只保留字母字符。处理连续字符:如果出现连续字符(不区分大小写),则输…...
DIY QMK量子键盘
最近放假了,趁这个空余在做一个分支项目,一款机械键盘,量子键盘取自固件名称QMK(Quantum Mechanical Keyboard)。 键盘作为计算机或其他电子设备的重要输入设备之一,通过将按键的物理动作转换为数字信号&am…...
mamba论文学习
rnn 1986 训练速度慢 testing很快 但是很快就忘了 lstm 1997 训练速度慢 testing很快 但是也会忘(序列很长的时候) GRU实在lstm的基础上改进,改变了一些门 transformer2017 训练很快,testing慢些,时间复杂度高&am…...
智慧消防营区一体化安全管控 2024 年度深度剖析与展望
在 2024 年,智慧消防营区一体化安全管控领域取得了令人瞩目的进展,成为保障营区安全稳定运行的关键力量。这一年,行业在政策驱动、技术创新应用、实践成果及合作交流等方面呈现出多元且深刻的发展态势,同时也面临着一系列亟待解决…...
解锁微服务:五大进阶业务场景深度剖析
目录 医疗行业:智能诊疗的加速引擎 电商领域:数据依赖的破局之道 金融行业:运维可观测性的提升之路 物流行业:智慧物流的创新架构 综合业务:服务依赖的优化策略 医疗行业:智能诊疗的加速引擎 在医疗行业迈…...
pip 安装 numpy 报错 AttributeError: module ‘pkgutil‘ has no attribute ‘ImpImporter‘
conda 环境下 pip 安装 numpy 1.x 版本,报如下错误 File "C:\Users\UserName\AppData\Local\Temp\pip-build-env-_lgbq70y\overlay\Lib\site-packages\pkg_resources\__init__.py", line 2191, in <module>register_finder(pkgutil.ImpImporter, fi…...
javascript-es6 (一)
作用域(scope) 规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问 局部作用域 函数作用域: 在函数内部声明的变量只能在函数内部被访问,外部无法直接访问 function getSum(){ //函数内部是函数作用…...
Babylon.js 中的 setHardwareScalingLevel和getHardwareScalingLevel:作用与配合修改内容
在 Babylon.js 中,Engine类提供了setHardwareScalingLevel和getHardwareScalingLevel方法,用于管理和调整渲染分辨率与屏幕分辨率的比例。这些方法在优化性能和提升画质方面非常有用。尤其是在某些平台不支持硬件反锯齿时,可以考虑使用setHar…...
二十三种设计模式-桥接模式
桥接模式(Bridge Pattern)是一种结构型设计模式,其核心思想是将抽象与实现解耦,让它们可以独立变化。桥接模式主要用于解决类的继承问题,避免由于继承而带来的类层次结构过于复杂和难以维护的问题。 1. 核心概念 桥接…...
jenkins-k8s pod方式动态生成slave节点
一. 简述: 使用 Jenkins 和 Kubernetes (k8s) 动态生成 Slave 节点是一种高效且灵活的方式来管理 CI/CD 流水线。通过这种方式,Jenkins 可以根据需要在 Kubernetes 集群中创建和销毁 Pod 来执行任务,从而充分利用集群资源并实现更好的隔离性…...
代码工艺:实践 Spring Boot TDD 测试驱动开发
TDD 的核心理念是 “先写测试,再写功能”,其过程遵循一个严格的循环,即 Red-Green-Refactor: TDD 的流程 1. Red(编写失败的测试) 根据需求,先编写一个测试用例,描述期望的行为。…...
【云安全】云原生-K8S-简介
K8S简介 Kubernetes(简称K8S)是一种开源的容器编排平台,用于管理容器化应用的部署、扩展和运维。它由Google于2014年开源并交给CNCF(Cloud Native Computing Foundation)维护。K8S通过提供自动化、灵活的功能…...
aws(学习笔记第二十六课) 使用AWS Elastic Beanstalk
aws(学习笔记第二十六课) 使用aws Elastic Beanstalk 学习内容: AWS Elastic Beanstalk整体架构AWS Elastic Beanstalk的hands onAWS Elastic Beanstalk部署node.js程序包练习使用AWS Elastic Beanstalk的ebcli 1. AWS Elastic Beanstalk整体架构 官方的guide AWS…...
反向代理模块。。
1 概念 1.1 反向代理概念 反向代理是指以代理服务器来接收客户端的请求,然后将请求转发给内部网络上的服务器,将从服务器上得到的结果返回给客户端,此时代理服务器对外表现为一个反向代理服务器。 对于客户端来说,反向代理就相当于…...
C语言的灵魂——指针(1)
指针是C语言的灵魂,有了指针C语言才能完成一些复杂的程序;没了指针就相当于C语言最精髓的部分被去掉了,可见指针是多么重要。废话不多讲我们直接开始。 指针 一,内存和地址二,编址三,指针变量和地址1&#…...
14-6-2C++STL的list
(一)list对象的带参数构造 1.list(elem);//构造函数将n个elem拷贝给本身 #include <iostream> #include <list> using namespace std; int main() { list<int> lst(3,7); list<int>::iterator it; for(itlst.begi…...
Linux 基础1
gcc的编译过程 预处理——编译——汇编——链接 Linux文件类型 普通文件,目录文件,管道文件,链接文件,块设备文件,字符设备文件,套接字文件 Linux系统下的软链接和硬链接有什么异同 linux中软链接和硬…...
Ubuntu Server 安装 XFCE4桌面
Ubuntu Server没有桌面环境,一些软件有桌面环境使用起来才更加方便,所以我尝试安装桌面环境。常用的桌面环境有:GNOME、KDE Plasma、XFCE4等。这里我选择安装XFCE4桌面环境,主要因为它是一个极轻量级的桌面环境,适合内…...
xarray转换nc文件经度范围:0-360更改为-180-180
原文见https://blog.csdn.net/weixin_44237337/article/details/119707332,因为觉得很实用就转载一下。 lon_name longitude #你的nc文件中经度的命名 ds[longitude_adjusted] xr.where(ds[lon_name] > 180,ds[lon_name] - 360,ds[lon_name]) ds (ds.swap_d…...
MySQL 基础学习(1):数据类型与操作数据库和数据表
MySQL 基础学习:数据类型与操作数据库和数据表 在这篇博客中,我们将深入学习 MySQL 的基础操作,重点关注数据库和数据表的操作,以及 MySQL 中常见的数据类型。希望本文能帮助你更好地理解和掌握 MySQL 的基本用法。 一、操作数据…...
一个简单的自适应html5导航模板
一个简单的 HTML 导航模板示例,它包含基本的导航栏结构,同时使用了 CSS 进行样式美化,让导航栏看起来更美观。另外,还添加了一些 JavaScript 代码,用于在移动端实现导航菜单的展开和收起功能。 PHP <!DOCTYPE htm…...
深入解析“Wholesome”的含义及用法
深入解析“Wholesome”的含义及用法 一、引言 在阅读英文材料时,我们经常会遇到一些词汇,它们的含义既有直接的字面意思,又带有丰富的情感色彩。“Wholesome”就是这样一个词。它表面上看似简单,但在不同语境中却有多重内涵。在…...
供水企业满意度调查报告
民安智库作为一家独立的第三方评估机构,致力于为供水企业提供全面的客户满意度调查服务。本报告将详细介绍民安智库的调查方法、结果和建议,以帮助供水企业更好地理解其服务质量和客户需求。 一、调查方法 民安智库首先对客户进行了分类,包括…...
实现B-树
一、概述 1.历史 B树(B-Tree)结构是一种高效存储和查询数据的方法,它的历史可以追溯到1970年代早期。B树的发明人Rudolf Bayer和Edward M. McCreight分别发表了一篇论文介绍了B树。这篇论文是1972年发表于《ACM Transactions on Database S…...
无人机微波图像传输数据链技术详解
无人机微波图像传输数据链技术是无人机通信系统中的关键组成部分,它确保了无人机与地面站之间高效、可靠的图像数据传输。以下是对该技术的详细解析: 一、技术原理 无人机微波图像传输数据链主要基于微波通信技术实现。在数据链路中,图像数…...
【Leetcode 热题 100】300. 最长递增子序列
问题背景 给你一个整数数组 n u m s nums nums,找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如, [ 3 , 6 , 2 , 7 ] [3,6,2,7] [3,6,2…...
macos的图标过大,这是因为有自己的设计规范
苹果官方链接:App 图标 | Apple Developer Documentation 这个在官方文档里有说明,并且提供了sketch 和 ps 的模板。 figma还提供了模板: Figma...
信号处理以及队列
下面是一个使用C和POSIX信号处理以及队列的简单示例。这个示例展示了如何使用信号处理程序将信号放入队列中,并在主循环中处理这些信号。 #include <iostream> #include <csignal> #include <queue> #include <mutex> #include <thread…...
