Android---Kotlin 学习008

定义类

示例:定义一个 Player 类

class Player {
    var name = "jack"
}

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

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

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 就不需要了。

val rolledValue
    get() = (1..6).shuffled.first()

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

防范竞态条件

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

示例:

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

初始化

主构造函数

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

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

在主构造函数里定义属性

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() 时才初始化。

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 属性还未完成初始化。

猜你喜欢

转载自blog.csdn.net/qq_44950283/article/details/134957600
008