Android---Kotlin 学习008

定义类

示例:定义一个 Player 类

Kotlin 复制代码
class Player {
    var name = "jack"
}

通过 Show Kotlin Bytecode(按两下 Shift 键),然后点击 Compile

示例2:创建 Player 对象,并给 name 属性赋值

Kotlin 复制代码
class Player {
    var name : String?= "jack"
}

fun main() {
    val p = Player()
    p.name = "rose" // 这种赋值方式其实调用的是 setName() 方法
}

field

针对你定义的每一个属性,Kotlin 都会产生一个 field,一个 get、以及一个 set,field 用来存储属性数据。你不能直接定义 field,Kotlin 会封装 field,保护它里面的数据,只暴露给 gett 和 set 使用。属性的 get 方法决定你如何读取属性值,每个属性都有 get 方法。set 方法决定你如何给属性赋值,所以只有可变属性才会有 set 方法。尽管 Kotlin 会自动提供默认的 get 和 set 方法,但在需要控制如何读写属性数据时,你可以自定义他们。

当我们需要自己定义 get() / set() 方法覆盖自动生成的get() / set() ,就需要用到 field 属性

计算属性

计算属性是通过一个覆盖的 get 或 set 运算符来定义,这时 field 就不需要了。

Kotlin 复制代码
val rolledValue
    get() = (1..6).shuffled.first()

这里就没有用到 field 。调用 get() 时,(1..6).shuffled.first() 的结果不会影响到 rolledValue。

防范竞态条件

如果一个类属性既可空又可变,那么引用它之前就必须保证它非空,一个办法是使用 also 标准函数。

示例:

Kotlin 复制代码
    var words : String? = "hello"
    fun saySomething(){
        words?.also { 
            println("Hello ${it.toUpperCase()}")
        }
    }

初始化

主构造函数

我们在 Player 类的定义头中定义一个主构造函数,使用临时变量为 Player 的各个属性提供初始值。在 Kotlin 中,为便于识别,临时变量(包括仅引用一次的参数),通常都会以下划线开头的名字命名。

在 java 中构造函数是与类名同名的,

在主构造函数里定义属性

kotlin 允许你不使用临时变量,而是直接用一个定义同时指定参数和类属性。通常,我们更细化用这种方式定义类属性,因为它会减少代码重复。

次构造函数

有主就要次,对应主构造函数的是次构造函数,我们可以定义多个次构造函数来配置不同的参数组合。

使用次构造函数,定义初始化代码逻辑。

默认参数

定义构造函数时,可以给构造函数的参数指定默认值,如果用户调用时不提供值参,就使用这个默认值。

初始化块

初始化块可以设置变量或值,以及执行有效性检查,如检查传给某构造函数的值是否有效,初始化块代码会在构造类实例时执行,即在构造函数里执行。

Kotlin 复制代码
    init {
        require(age > 0){"age must be positive"}
        require(name.isNotBlank()){"player must have a name."}
    }

初始化顺序

  1. 主构造函数里声明的属性;2. 类级别的属性赋值;3. init 初始化块里的属性赋值和函数调用;4. 次构造函数里的属性赋值和函数调用。

延长初始化

当用到时才初始化。使用 lateinit 关键字相当于做了一个约定:在用它之前负责初始化。

实例:当我们用到 tool 时,才初始化 tool 。即在 main() 方法里调用 ready() 时才初始化。

Kotlin 复制代码
class Student {
    // lateinit 可以使变量延长初始化
    lateinit var tool : String

    fun ready(){
        tool = "pen"
    }
    fun examination(){
        println(tool)
    }
}

fun main() {
    val student = Student()
    // 先 准备 笔
    student.ready()
    // 然后才能考试
    student.examination()
}

如果在 ready() 之前调用 examination(),那么就会抛异常。表示延长初始化变量没有被初始化。

针对上面的异常,只要无法确认 lateinit 变量是否完成初始化,可以执行 isInitialized 检查。

惰性初始化

延迟初始化并不是推后初始化的唯一方式,你也可以暂时不初始化某个变量,直到首次使用它,这个叫做惰性初始化。

初始化陷阱1

在使用初始化块时,顺序非常重要,你必须保证块中的所有属性已完成初始化。

示例:

在上面的初始化顺序 中,我们知道类级别属性 是在初始化块属性之前的,所以视乎上面的写法没有太大问题。当时当我们反编译后(show kotlin Bytecoe),发现并不是我们想的那样。

在 konlin 中编译是按从上到下的顺序进行的。所以上面代码中,blood 的声明是在 init{} 块之后,那么这时就会编译出错(此时只是编译,还不是属性初始化,所以不要把二者搞混了)。

正确写法如下:

初始化陷阱2

下面这段代码在编译时没有问题,因为编译器看到 name 属性已经在 init 块 里初始化了,但代码一运行,就会抛出空指针异常,因为 name 属性还没赋值,firstLetter() 函数就应用它了

将 init{} 块的两行代码的先后顺序调换一下,就能解决上述问题:

初始化陷阱3

因为编译器看到所有属性都初始化了,所以代码编译没问题,但运行结果却是 null,问题出在哪?在用 initialPlayerName 函数初始化 playerName 时,name 属性还未完成初始化。

相关推荐
Kapaseker43 分钟前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴1 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭11 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab12 小时前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe17 小时前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农1 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos
鹏程十八少1 天前
4.Android 30分钟手写一个简单版shadow, 从零理解shadow插件化零反射插件化原理
android·前端·面试
Kapaseker1 天前
一杯美式搞定 Kotlin 空安全
android·kotlin
三少爷的鞋1 天前
Android 协程时代,Handler 应该退休了吗?
android
火柴就是我2 天前
让我们实现一个更好看的内部阴影按钮
android·flutter