类与对象¶
Kotlin 的类语法比 Java 更紧凑,但背后的概念并不简单。主构造函数、属性、默认 final、数据类、对象声明等设计都会影响 API 的可读性和可维护性。
类声明¶
class User
带主构造函数和属性:
class User(val id: Long, var name: String)
这行代码同时做了几件事:
- 声明类
User。 - 声明主构造函数参数
id和name。 - 把
id声明为只读属性。 - 把
name声明为可变属性。
Java 近似写法:
public class User {
private final long id;
private String name;
public User(long id, String name) {
this.id = id;
this.name = name;
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
属性¶
Kotlin 的属性不是简单字段。属性通常包含:
- backing field,也就是实际存储值的字段。
- getter。
- 可变属性的 setter。
class Rectangle(val width: Int, val height: Int) {
val area: Int
get() = width * height
}
area 没有保存字段,每次访问时计算。
初始化块¶
主构造函数不能直接写代码,初始化逻辑放在 init 块:
class User(val name: String) {
init {
require(name.isNotBlank()) { "name 不能为空" }
}
}
init 块按声明顺序执行。复杂初始化逻辑要注意属性声明顺序,避免在初始化完成前访问尚未准备好的状态。
次构造函数¶
class User(val name: String) {
constructor(firstName: String, lastName: String) : this("$firstName $lastName")
}
有主构造函数时,次构造函数必须直接或间接委托给主构造函数。
多数 Kotlin 类不需要多个构造函数。默认参数和工厂函数通常更清晰:
class ServerConfig(
val host: String,
val port: Int = 8080
)
继承:默认 final¶
Kotlin 类默认不可继承:
class User
// class Admin : User() // 编译错误
要允许继承,显式写 open:
open class Shape
class Rectangle : Shape()
方法同样默认不可覆盖:
open class Animal {
open fun speak() {
println("...")
}
}
class Dog : Animal() {
override fun speak() {
println("woof")
}
}
Java 默认允许继承和覆盖,除非使用 final。Kotlin 反过来:默认关闭继承,只有明确设计为扩展点时才打开。这能减少脆弱基类问题。
抽象类¶
abstract class Repository<T> {
abstract fun findById(id: Long): T?
fun requireById(id: Long): T =
findById(id) ?: error("找不到数据:$id")
}
抽象类本身可以被继承,抽象成员必须由子类实现。
接口¶
interface Clickable {
fun click()
fun doubleClick() {
click()
click()
}
}
Kotlin 接口可以包含默认方法实现,也可以声明属性:
interface Named {
val name: String
}
接口属性不一定有字段,它只是要求实现类提供这个属性。
数据类¶
用于承载数据的类可以声明为 data class:
data class User(val id: Long, val name: String)
编译器会基于主构造函数中的属性生成常用方法:
equals()hashCode()toString()copy()componentN()解构函数
示例:
val user = User(1, "Ada")
val renamed = user.copy(name = "Grace")
println(user)
println(renamed)
Java 16+ 的 record 与 Kotlin data class 很像,但 Kotlin 数据类更早出现,也有不同约束和互操作细节。迁移时不要简单认为二者完全等价。
对象声明¶
Kotlin 没有 Java 风格的 static 关键字。单例可以用 object:
object AppConfig {
val appName = "Demo"
}
println(AppConfig.appName)
这会声明一个线程安全初始化的单例对象。
伴生对象¶
类内需要类似静态成员时,使用伴生对象:
class User private constructor(val name: String) {
companion object {
fun create(name: String): User {
require(name.isNotBlank())
return User(name)
}
}
}
val user = User.create("Ada")
从 Kotlin 调用时像静态方法;从 Java 调用时会有伴生对象相关的字节码形态。需要更贴近 Java 静态调用时,可以考虑 @JvmStatic。
密封类简述¶
密封类限制继承层级,常用于表达有限状态:
sealed interface UiState
data object Loading : UiState
data class Success(val data: String) : UiState
data class Failure(val message: String) : UiState
fun render(state: UiState): String =
when (state) {
Loading -> "加载中"
is Success -> "成功:${state.data}"
is Failure -> "失败:${state.message}"
}
密封层级配合 when 可以得到编译期穷尽检查。新增状态时,遗漏处理的地方会暴露出来。
实践建议¶
- 默认使用不可变属性
val,确实需要修改时再用var。 - 只有为继承设计的类才标记
open。 - 数据承载类型优先考虑
data class。 - 有限状态优先考虑密封类或密封接口,而不是字符串常量。
- Java 框架需要无参构造、代理或反射时,提前确认 Kotlin 插件和注解配置。
参考¶
- 官方类文档:https://kotlinlang.org/docs/classes.html