重学Kotlin(四)面向对象

重学Kotlin(四)面向对象

一、Kotlin 的类构造器

Kotlin 的类构造器(Constructors)分为 主构造器次构造器 ,再加上 初始化块 init,一起组成完整的对象初始化流程。

1. 主构造器(Primary Constructor)

最常用、最简洁的构造方法,直接写在类名后面:

kotlin 复制代码
class User(val name: String, val age: Int)

特点:

  • 定义在类名后面的小括号中
  • 默认是 public
  • 参数如果加 val/var 会自动生成对应字段
  • 不能包含逻辑(逻辑写在 init 中)

2. init 初始化块

主构造器不能写逻辑,所以逻辑写在 init 里:

kotlin 复制代码
class User(val name: String, val age: Int) {

    init {
        println("User created: $name, $age")
    }
}

执行顺序:主构造器参数 -> init 块

3. 次构造器(Secondary Constructor)

写在类内部,用 constructor 关键字:

kotlin 复制代码
class User(val name: String) {

    var age: Int = 0

    constructor(name: String, age: Int) : this(name) {
        this.age = age
    }
}

特点:

  • constructor(...)
  • 必须最终调用主构造器(如果有主构造器)
  • 可以包含逻辑

4. 没有主构造器时

kotlin 复制代码
class User {
    var name: String = ""
    var age: Int = 0

    constructor(name: String, age: Int) {
        this.name = name
        this.age = age
    }
}

如果没有主构造器,则 次构造器不需要调用 this()

5. 主构造器与默认值

最常用的写法:

kotlin 复制代码
class User(
    val name: String,
    val age: Int = 18
)

这样可以直接:

kotlin 复制代码
val u = User("Tom")         // age = 18
val u2 = User("Tom", 20)

Kotlin 强烈推荐这种写法代替多个构造器重载,更简洁。

6. 主构造器可以私有化

常用于工厂方法:

kotlin 复制代码
class User private constructor(val name: String) {
    companion object {
        fun create(name: String) = User(name)
    }
}

DCL 双重检查锁定单例:

kotlin 复制代码
class Singleton private constructor() {

    companion object {
        @Volatile
        private var instance: Singleton? = null

        fun getInstance(): Singleton =
            instance ?: synchronized(this) {
                instance ?: Singleton().also { instance = it }
            }
    }
}

二、Kotlin 的可见性

修饰符 类内部 子类 同一文件 同一模块 其他模块
public(默认)
internal
protected
private

与 Java 差异:

(1)internal

表示模块内可访问 例如一个 Gradle module 不同 module 之间不能访问 internal。

kotlin 复制代码
internal class ApiClient
(2)顶级声明可见性

顶级声明指的是在文件内直接定义类、函数、属性等。

  1. 顶级声明不支持 protected,顶级声明没有父类概念,所以 Kotlin 语义不允许顶级使用 protected
  2. 顶级声明被 private 修饰,表示文件内可见

三、Kotlin 属性

1. Kotlin 属性是什么?

Kotlin 中 属性 = 字段(field) + getter/setter 方法 的组合。

kotlin 复制代码
class User {
    var name: String = "Tom"  // 属性
    val age: Int = 18          // 只读属性
}
  • var 可读写
  • val 只读(相当于 final + getter)

Kotlin 不像 Java,需要手动写 getter/setter,编译器自动生成。

2. 自定义 getter / setter

kotlin 复制代码
class User {
    var name: String = "Tom"
        get() = field.uppercase()       // 自定义 getter
        set(value) { field = value.trim() } // 自定义 setter
}
  • field = 实际存储的字段
  • getter/setter 可以加逻辑
  • val 只能写 getter(不能写 setter)

3. 常量属性

kotlin 复制代码
const val VERSION = "1.0" // 顶层常量
  • 必须是顶层或 companion object 内
  • 类型必须是基本类型或 String
  • 编译期常量,可在 Java 中访问

4. 延迟初始化属性

(1)初始化为null
kotlin 复制代码
    var name: String? = null

    fun init(name: String?) {
        this.name = name
        name?.substring(0, 1)
    }

不推荐,写法等价 Java 代码,使用的时候需要判断空指针。

(2)使用lateinit
kotlin 复制代码
class User {
    lateinit var token: String

    fun initToken() {
        token = "xxx"
    }
}
  • 只能用于 var
  • 非原始类型(非 Int/Long 等)
  • 使用前未初始化会报异常 UninitializedPropertyAccessException
  • 适用于:依赖注入(DI)、开发者完全确认变量生命周期的场景
(3)lazy

本质上用到的是属性代理

kotlin 复制代码
val data: String by lazy {
    println("lazy init")
    "Hello"
}
kotlin 复制代码
// 相关源码
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    ...
    override val value: T
        get() {
            ...
            return synchronized(lock) {
                ...
                val typedValue = initializer!!() // 调用传入的初始化函数
                _value = typedValue
                initializer = null
                typedValue
            }
        }
}
  • 只读 val
  • 第一次访问时初始化
  • 默认线程安全

四、代理 Delegate

1. 属性代理

上文已经介绍了 lazy 属性代理,本质是代理了 getter。 下面介绍 Kotlin 自带的另外几种属性代理,都在 Delegates 类下。

(1)observable ------ 可观察属性
  • 类型 :可变 var
  • 作用:属性值变化时回调
  • 常用场景:UI 数据绑定、监控状态变化、日志记录
kotlin 复制代码
import kotlin.properties.Delegates

var name: String by Delegates.observable("Tom") { prop, old, new ->
    println("$old -> $new")
}

fun main() {
    name = "Jerry" // 输出:Tom -> Jerry
    name = "Spike" // 输出:Jerry -> Spike
}
(2)vetoable ------ 可阻止修改的属性
  • 类型 :可变 var
  • 作用:属性值变化时通过 lambda 判断是否允许修改
  • 返回 true → 修改属性;false → 拒绝修改
  • 常用场景:值校验、约束属性、范围限制
kotlin 复制代码
var age: Int by Delegates.vetoable(0) { prop, old, new ->
    new >= 0
}

fun main() {
    age = 10  // OK
    age = -5  // 被拒绝,age 仍为 10
}
(3)notNull ------ 非空延迟初始化(var)
  • 类型 :可变 var
  • 作用:属性在使用前必须初始化,否则抛异常
  • 常用场景:依赖注入(DI)、View 或 late binding
kotlin 复制代码
var data: String by Delegates.notNull()

fun main() {
    // println(data) // ❌ 抛异常
    data = "Hello"
    println(data) // ✅ Hello
}
(4)自定义代理

可实现接口 ReadWritePropertyReadOnlyProperty 实现自定义代理:

kotlin 复制代码
class CustomDelegate(var value: String = "") : ReadWriteProperty<Any?, String> {
    override fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "Delegate $value"
    }
    override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        this.value = "new $value"
    }
}

var name: String by CustomDelegate("Tom")

fun main() {
    println(name) // 输出: Delegate Tom
    name = "jerry"
    println(name) // 输出: Delegate new jerry
}

2. 接口代理

基本用法:

  1. 声明一个接口并且实现
  2. 在类中声明 by 代理对象
  3. 类就会自动实现接口,并把接口方法调用委托给这个对象

本质 = 组合 + 委托,替代 Java 中的手动代理模式。

kotlin 复制代码
interface Api {
    fun a()
    fun b()
}

class ApiImpl : Api {
    override fun a() {
        println("invoke a")
    }

    override fun b() {
        println("invoke b")
    }
}

class ApiWrapper(val api: ApiImpl) : Api by api {
    override fun a() {
        println("Before")
        api.a()
        println("After") // 输出: Before \n invoke a \n After
    }
}

适用场景:

  1. 装饰器 / Wrapper 模式:不改变原接口实现,添加行为
  2. 代理模式(Proxy):Kotlin 语法简洁,不用手动写每个方法的转发代码

五、单例 object

1. 基本用法

在 Kotlin 中,object 是最简单、最安全的单例实现方式,相当于 Java 中的"懒汉式"写法。

kotlin 复制代码
object Singleton {
    val name = "Tom"

    fun printName() {
        println(name)
    }
}

fun main() {
    Singleton.printName()  // Tom
}

2. 扩展用法

(1)接口实现
kotlin 复制代码
interface Logger {
    fun log(msg: String)
}

object ConsoleLogger : Logger {
    override fun log(msg: String) {
        println("Log: $msg")
    }
}
(2)伴生对象 companion object
kotlin 复制代码
class MyClass {
    companion object {
        fun create() = MyClass()
    }
}

fun main() {
    val obj = MyClass.create()
}
  • 伴生对象也是单例
  • 可以实现接口或委托

3. @JvmField 注解与 @JvmStatic 注解

由于 Kotlin 是一门跨平台语言(不局限于 JVM),语言本身没有 Java 风格的静态字段/静态方法语义。为增强与 Java 的互操作性,Kotlin 提供了 @JvmField@JvmStatic 两个注解:

kotlin 复制代码
object Singleton {
    @JvmField val name = "Tom"

    @JvmStatic fun printName() {
        println(name)
    }
}

@JvmField 的作用:

java 复制代码
// 如果没有加入注解,Java 需要通过 INSTANCE 的 getter 来访问
Singleton.INSTANCE.getName();

// 加入注解后,不会生成 getter/setter 方法,并且可以直接访问属性
String name = Singleton.name;

@JvmStatic 的作用:

java 复制代码
// 如果没有加入注解,Java 需要通过 INSTANCE 来访问
Singleton.INSTANCE.printName();

// 加入注解后,生成静态方法,可以直接访问
Singleton.printName();

六、数据类 data class

Kotlin 的 data class 是一个非常核心的语言特性,用来快速定义携带数据的类,同时自动生成很多常用方法(如 toStringequalshashCodecopy 等)。

1. 基本使用

kotlin 复制代码
data class User(
    val name: String,
    val age: Int
)
  • 关键词data
  • 主构造函数:至少有一个参数
  • 参数 :必须至少有一个 valvar,用于生成字段和方法

2.componentN()(解构函数)

kotlin 复制代码
fun main() {
    val user = User("Tom", 20)
    val (name, age) = user

    println(user.component1()) // Tom
    println(user.component2()) // 20
    println(name) // Tom
    println(age)  // 20

    // 字节码反编译成 Java,可以看到生成了 componentN() 函数
   @NotNull
   public final String component1() {
      return this.name;
   }

   public final int component2() {
      return this.age;
   }
}

3. data class 局限性

限制 说明
构造函数参数 必须至少有一个 valvar
继承 不能直接继承其他类(除非是 open data class,但是 Kotlin 不推荐)
可变性 可用 var 修改字段,但 val 保持不可变

这些限制可能导致某些 ORM(例如 JPA)使用不便 ,因此 data class 更适合用于 DTO 层。Kotlin 官方提供了 allopennoarg 插件来改善兼容性:

kotlin 复制代码
// 让指定注解的类自动变成 open
// 自动把它的成员方法也变成 open
kotlin("plugin.allopen") version "1.9.23"
// 给带特定注解的类自动生成一个 无参构造函数
kotlin("plugin.noarg") version "1.9.23"


// 打上自定义注解@DomainModel的会生效
allOpen {
    annotation("com.example.DomainModel")
}

noArg {
    annotation("com.example.DomainModel")
}

七、枚举类 enum class

1. 最基础的枚举

kotlin 复制代码
enum class Direction {
    NORTH, SOUTH, EAST, WEST
}

使用:

kotlin 复制代码
val d = Direction.NORTH

2. 枚举带构造函数(携带属性)

Kotlin 的枚举本质上和普通类差不多,也能有构造器:

kotlin 复制代码
enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

使用:

kotlin 复制代码
val red = Color.RED.rgb

3. 枚举可以定义方法

kotlin 复制代码
enum class State {
    IDLE,
    RUNNING,
    FINISHED;

    fun canRun(): Boolean = this == IDLE
}

4. 每个枚举常量定义自己的行为(匿名类)

跟 Java 类似,可以重写抽象方法:

kotlin 复制代码
enum class Animal {
    DOG {
        override fun sound() = "Wang!"
    },
    CAT {
        override fun sound() = "Meow"
    };

    abstract fun sound(): String
}

5. 枚举实现接口

kotlin 复制代码
interface Printable {
    fun print()
}

enum class Level : Printable {
    LOW {
        override fun print() = println("Low")
    },
    HIGH {
        override fun print() = println("High")
    }
}

6. 枚举工具方法

name → 字符串名称
kotlin 复制代码
val name = Direction.NORTH.name // "NORTH"
ordinal → 序号(从 0 开始)
kotlin 复制代码
val index = Direction.NORTH.ordinal // 0
values() → 所有枚举值
kotlin 复制代码
Direction.values()
valueOf() → 根据名称获取枚举
kotlin 复制代码
val d = Direction.valueOf("SOUTH")

八、密封类 sealed class

Kotlin 密封类(sealed class) 是 Kotlin 非常强大的特性,用来表示受限制的类层级。它的核心作用是: **让编译器知道某个类型所有可能的子类,从而实现更安全的 when 判断、建模各种状态、减少错误。**这点在单元测试里面很有用

1. 密封类是什么?

kotlin 复制代码
sealed class Result

密封类的特点:

  • 同一个文件里才能定义它的子类(Kotlin 1.5+ 允许同一个包的不同文件,但仍有限制)
  • 所有子类集合已知且有限
  • 适合表示「有限状态机」、「固定状态」

2. 密封类 vs 抽象类

特性 sealed class abstract class
子类是否有限 ✔ 是(受限制) ✘ 否
when 是否强制覆盖所有子类 ✔ 是 ✘ 否
适合表示状态 ✔ 最佳 一般

3. 密封类可以是 data class、object、普通 class

kotlin 复制代码
sealed class LoginState {
    object Idle : LoginState()
    data class Success(val user: User) : LoginState()
    data class Error(val reason: String) : LoginState()
    object Loading : LoginState()
}

4. 密封接口(sealed interface)

Kotlin 1.5+ 引入:

kotlin 复制代码
sealed interface UiState

data class Success(val list: List<String>) : UiState
object Loading : UiState
object Empty : UiState

与密封类类似,但可以多继承。


5. 密封类的优势总结

最安全的状态表达

避免写"字符串状态"、"枚举状态",更类型安全。

when 表达式无 else

编译器知道所有可能子类 → 帮你检查有没有漏掉分支。在做单元测试可以避免else死分支

可以携带不同类型的数据

比 enum 更灵活(enum 不能为每个 case 定义不同字段)。

更适合 UI 层、状态管理、网络层建模 Compose、MVI 最常用模式。

6.示例:UI 页面状态

例如列表页面:

kotlin 复制代码
sealed class PageState<out T> {
    object Loading : PageState<Nothing>()
    object Empty : PageState<Nothing>()
    data class Success<T>(val data: T) : PageState<T>()
    data class Error(val message: String) : PageState<Nothing>()
}

使用:

kotlin 复制代码
when (state) {
    PageState.Loading -> showLoading()
    PageState.Empty -> showEmpty()
    is PageState.Success -> showData(state.data)
    is PageState.Error -> showError(state.message)
}

7. sealed class vs enum class(区别)

比较点 sealed class enum class
子类数据结构 每个子类不同 所有枚举结构相同
可携带不同数据
是否可以继承其他类
适用场景 状态有限且可携带数据 固定常量

如果你要 固定常量 → enum 如果你要 状态 + 可携带不同数据 → sealed class

相关推荐
国霄1 小时前
(6)Kotlin/Js For Harmony——ArkTs 开发工具套件
kotlin·harmonyos
路人甲ing..6 小时前
Ubuntu怎么安装tar.gz (android-studio为例)
linux·ubuntu·kotlin·android studio
啃火龙果的兔子1 天前
Kotlin 修改安装到桌面后的应用图标
开发语言·kotlin·harmonyos
来来走走1 天前
Android开发(Kotlin) ViewModel基本用法
android·开发语言·kotlin
用户69371750013841 天前
6.Kotlin 流程控制:循环控制:while 与 do/while
android·后端·kotlin
Entropless1 天前
Kotlin 可以预判你的预判?Kotlin 高级特性 之 Contracts
android·kotlin
用户6578300034921 天前
kotlin 中 return@key 用法
kotlin
来来走走1 天前
Android开发(Kotlin) 协程
android·java·kotlin
邮专薛之谦2 天前
Kotlin 全知识点复习+详细梳理
windows·kotlin·android studio·idea