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 写出结构干净的业务代码了。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐