KMP学习day2--Kotlin类与对象核心语法
KMP学习day2–Kotlin类与对象核心语法
引入:为什么 Kotlin 的类值得单独讲
如果你写过 Java,第一次看 Kotlin 的类定义大概率会愣一下:构造函数怎么直接写在类名后面?data class 是什么?object 关键字为什么能当单例用?这些都是 Kotlin 在 Java 基础上做的语法减法——用更少的代码表达同样的结构,同时把「样板代码」这类出错点直接消灭在语言层面。
对于做鸿蒙开发的同学,这一套概念在 ArkTS 里几乎都有对应物(class、interface、命名对象),理解 Kotlin 的写法后切换成本很低。本文把 Kotlin 类与对象最核心的四块讲清楚:主构造函数与 init 块、data class、object 与 companion object。
主构造函数与 init 块
Kotlin 把「声明一个带属性的类」压缩到了类名后的一对括号里:
class Person(
val name: String,
var age: Int
) {
init {
require(age >= 0) { "age 不能为负数" }
println("Person $name 初始化完成")
}
}
几个关键点:
- 主构造函数是类头的一部分。括号里写
val name: String,等于同时声明了构造参数和只读属性,Java 里「写字段 → 写构造参数 → 构造器里赋值」三步变一步。 val生成只读属性,var生成可变属性。不写val/var的参数只是普通构造参数,不会成为属性。init块是主构造函数的函数体。主构造函数不能直接写逻辑,初始化校验、日志等逻辑放进init,它在类实例化时按出现顺序执行。
如果还需要次要构造函数,用 constructor 关键字定义,且必须委托给主构造函数:
class Person(val name: String, var age: Int) {
constructor(name: String) : this(name, 0)
}
ArkTS 类比:ArkTS 的 class 构造函数写在类体内,参数要手动赋给 this.xxx;Kotlin 的主构造函数相当于「构造函数签名 + 属性声明 + 赋值」三合一。
data class:按值比较的载体
业务开发里大量类只是「数据的容器」:用户信息、配置项、网络响应。这类类在 Java 里要手写 equals、hashCode、toString,写错一个就埋雷。Kotlin 用 data class 一个关键字解决:
data class User(
val id: Long,
val name: String,
val vip: Boolean
)
fun main() {
val a = User(1, "小明", false)
val b = User(1, "小明", false)
println(a == b) // true:按值(结构)比较
println(a === b) // false:不是同一个对象
println(a) // User(id=1, name=小明, vip=false)
println(a.copy(name = "小红")) // User(id=1, name=小红, vip=false)
}
data class 自动生成了:
equals()/hashCode():基于构造属性按值比较,两个内容相同的实例相等;toString():直接打印所有字段,调试利器;copy():不可变对象更新字段的标配——不改原对象,生成改了指定字段的新副本。
普通 class 则默认是引用比较(== 在 Kotlin 里等价于 equals,普通类没重写时就是比对象地址),同一个表单提交两次会得到两个「不相等」的 User。这就是实际项目里推荐数据模型都用 data class 的原因。
object:一步到位的单例
Kotlin 没有静态成员的写法,取而代之的是 object——直接声明一个「只有一个实例的类」:
object AppConfig {
var theme: String = "dark"
fun currentTheme(): String = theme
}
fun main() {
AppConfig.theme = "light" // 直接用,不需要 getInstance()
println(AppConfig.currentTheme())
}
object声明在顶层(或类内)时,实例在第一次访问时懒加载创建,天然线程安全;- 它还可以继承类和接口,比 Java 手写单例(私有构造 + 静态 getInstance + 双重检查锁)简洁得多。
ArkTS 类比:ArkTS 里通常用「导出一个 const 对象」或静态成员实现单例,object 的效果与之等价。
companion object:类级别的成员
有时候不想造一个全局单例,只想在某个类上挂「类级别的」常量或工厂方法,这时用 companion object(伴生对象):
class Counter {
companion object {
const val MAX_COUNT = 10
private var created = 0
fun create(): Counter {
require(created < MAX_COUNT) { "超出实例上限" }
created++
return Counter()
}
}
}
fun main() {
val c = Counter.create()
println(Counter.MAX_COUNT) // 像静态成员一样通过类名访问
}
- 一个类只能有一个
companion object,访问时直接用类名,不用写Companion; const val只能放在companion object(或顶层),编译期内联为常量;- 常见用法:
TYPE_JSON = "application/json"这类常量、of()/create()这类工厂方法、跨实例共享的计数器。
注意区别:object 是一个独立的对象(比如全局配置、缓存管理器),companion object 是挂在某个类上的静态区(常量、工厂方法)。需要「类名直接访问」用后者,需要「全局唯一实例」用前者。
实际应用:一次建模的完整例子
把上面四块串起来,看一个典型业务建模:
data class Article(
val id: Long,
val title: String,
val author: String,
val liked: Boolean = false
) {
fun withLike() = copy(liked = true) // 不可变更新
}
object ArticleRepo {
private val cache = mutableListOf<Article>()
fun add(article: Article) { cache.add(article) }
fun all(): List<Article> = cache.toList()
}
class ArticleParser {
companion object {
fun fromJson(json: String): Article? {
// 实际项目中用 kotlinx.serialization
return if (json.isNotBlank()) Article(1, "示例", "佚名") else null
}
}
}
Article用data class+ 不可变属性 +copy(),界面状态管理里这样建模最安全;ArticleRepo用object做内存缓存单例,全局一份;ArticleParser的解析入口挂在companion object上,调用方写ArticleParser.fromJson(...),语义清晰。
这三类结构在 Flutter(Dart 的 factory 构造、顶层变量)、ArkTS(static、模块级导出)里都能找到对应写法,理解意图比记语法更重要。
小结
- 主构造函数写在类头,
val/var参数直接成为属性,init块承担初始化逻辑; data class自动生成equals/hashCode/toString/copy,按值比较,适合一切数据模型;object声明即单例,懒加载且线程安全;companion object提供类级别的常量与工厂方法,相当于「静态区」;- 迁移视角:
data class≈ ArkTS 中手写 equals 的 class,object≈ 模块级单例导出,companion object≈ static 成员。掌握这四件套,就能用 Kotlin 写出结构干净的业务代码了。
更多推荐

所有评论(0)