Kotlin - 属性 getter/setter、lateinit、by lazy

一、属性声明

权限修饰符 var|val 属性名称:属性类型 = 初始化语句

  • Kotlin中非抽象的属性必须初始化,因为空类型安全。
  • 属性不能缺少初始化语句,要么在定义属性的地方、要么在构造中、要么在 init{ } 代码块里,否则会编译错误。
  • 声明为 val 的属性不能有自定义的 set() 语句,因为不能再次赋值。
  • 成员属性初始化顺序:主构造 > 声明处 = init{ } > 次构造。属性声明和init按顺序执行。
  • 属性在直接或间接调用前,一定要完成初始化。
//id在主构造中初始化
class Demo(var id: Long, grade: Int) {
    //grade在主构造中初始化
    var grade = grade
    //age在声明处初始化
    var age = 18
    //name在 init{} 中初始化
    var name: String
    init {
        this.name = "张三"
    }
    //hobbit在次构造中赋值
    var hobbit: String? = null
    constructor() : this(123L, 5) {
        this.hobbit = "打球"
    }
}

一些调用前未初始化的错误示范: 

class Demo(str: String) {
    //直接调用属性(编译器会提示)
    init {
        val age = num + 4   //提示num需要初始化
    }
    val num = 100
    //间接调用属性1(编译器不会提示)
    private val name: String
    private fun show() = name[0]
    init {
        print(show())   //初始化前就调用了
        name = "jack"
    }
    //间接调用2(编译器不会提示)
    val text2: String = initText()  //text1初始化前就调用了
    val text1: String = str
    private fun initText() = text1
}

二、幕后字段 Backing Field

        在Kotlin中属性作为一级语言特性,通常情况下集幕后字段(field储值变量)+ 访问器(getter读访问器、setter写访问器)于一身,无论是声明【var age: Int】、赋值【user.age = 18】、取值【println(user.age)】,从字面上看都是 age 这个属性本身,是一个整体。而只有在我们需要自定义访问器的时候才会区分这三者。

        例如自定义 setter 的时候,如果不写成幕后字段 field = value,不管是 age = value 还是 this.age = value 都会报错,因为属性 age 的赋值就是setter,显然不能递归调用。

        如果属性的访问器至少有一个使用默认实现,或者自定义的访问器中使用了 field ,那么就会提供幕后字段,用 field 关键字表示,主要用于自定义 getter/setter 时使用,也只能在 getter/setter 中访问。

class Demo{
    var id: Long = 0
        get() = field
        set(value) { field = value }
}

三、访问器 getter & setter

  • 一般用于让属性在不同条件下有不同值。
  • 使用 var 修饰的属性默认拥有 getter 和 setter,使用 val 修饰的属性默认只有 getter。可见性默认和字段的可见性一致,也可以自定义。
  • 使用 实例.属性名 的写法会自动编译为 getter 或 setter,同时允许自定义 getter 和 setter,只能在类体中定义。
  • getter 没有参数列表,返回值类型与属性类型相同因此可以自动推导不写。不能在 getter 里再调用本属性,会出现无限循环导致堆栈溢出错误。
  • setter 是一个没有返回值的函数。单个参数的时候一般使用 value 表示(非必须),field 表示本属性的值(相当于this.本属性名)。
  • 属性初始化后,自定义 getter/setter 里才能使用 field。属性未初始化,getter&setter 必须手动声明且不能使用 field。
class Demo() {
    //默认自动生成
    var id: Long = 0
        get() = field
        set(value) { field = value }
    //后接等号表达式或值
    var age = 18
        get() = 20
        set(value) = if (value > 0) field = value else field = 0
    //后接大括号语句
    var name = "haha"
        get() { return "getter" }
        set(value) { field = "settet" + value }
}

四、类外的属性

在类外定义的属性是包级的,该属性会被编译为单独的一个类(原类名Kt)。单独想调用该类的话,导包后Kotlin直接使用字段,Java调用getXXX()同名方法。

//aa.Demo.kt
val number = 10086    //类体外定义的包级属性
class Demo(){}

//编译会生成一个DemoKt类,反编译后内容如下
public final class DemoKt{
    private static final int number = 10086;    //number被编译为这个类的私有静态字段
    public static final int getNumber(){        //并拥有一个默认的getXXX()同名方法
        return number;
    }
}

//Koltin使用
import aa.number    //因为是包级变量,使用 包名.属性 名方式导入
val b = number    //直接使用字段

//Java使用
import aa.DemoKt;    //导入自动生成的class
int b = DemoKt.getNumber();    //使用默认的getXXX()同名方法

编译期常量 const val 定义在类外,反编译后可以看到它就是Java中的常量,但是有几点限制:

  • 只能定义在类外或对象(Object)内。
  • 只能使用 String 或 基本数据类型 初始化。
  • 不能自定义 getter(不需要可直接调用)。
//aa.Demo.kt
const val number = 10086
class Demo(){}

//反编译内容如下:
public final class DemoKt{
    public static final number = 10086;
}

Kotlin中使用在注解参数中的属性,只能是编译期常量(其他形式的属性都不能使用在注解的参数里)。

const val DEPRECATED_MESSAGE = "This is deprecated."
@Deprecated(DEPRECATED_MESSAGE) fun foo() {……}

五、延迟初始化 lateinit

当有些属性不需要在声明的时候初始化(局部变量抽成类中全局变量如findViewById的时候)或者需要外部来初始化,可以使用 latrinit 来延迟初始化。

  • 使用 lateinit 修饰属性后,编译器不会再做非空检查,不初始化也不会报错,所以需要做到自己可控(无论是在类中还是类外要对属性赋值),否则在使用之前未初始化,调用会报错 UninitializedPropertyAccessException。
  • 不可修饰基本数据类型和可空类型。因为非空的数据类型可用 null 标记未初始化并在访问的时候引发异常,而原生数据类型和可空的类型没办法用null标记。
  • 不能有自定义的 getter/setter。
  • 1.2以后可以定义在主构造和局部,尽量还是在类中定义。
  • lateinit 只能用在 var 类型上,并可在任意位置初始化多次,有幕后字段(Backing Fields)。
初始化方式适用的数据类型缺点
声明属性的时候就给一个默认值基本类型
使用Delegates.notNull()基本类型、引用类型

①属性初始化必须在属性使用之前,否则报错

②不支持外部注入工具将它直接注入到Java字段中

使用 lateinit引用类型属性初始化必须在属性使用之前,否则报错
class Demo() {
    //lateinit初始化
    lateinit var name: String
    fun initName(){ name = "张三" }    //函数名可以随便取
    fun show() = ::name.isInitialized   //检查是否初始化
    //by lazy初始化
    val id: Long by lazy {
        println("初始化:" + System.currentTimeMillis())
        123L
    }
}

fun main() {
    val aa = Demo()
    println(aa.show())  // 打印:false
}

六、封装、懒加载

6.1 推荐写法

 关联知识点 :属性委托

class Demo {
    //var属性
    var num: MutableLiveData<Int>? = null
        get() {
            println("打印就说明var加载了")
            if (field == null) {    //实现懒加载
                field = MutableLiveData<Int>(0)
            }
            return field
        }
        private set //对外暴露只读

    //val属性,本身只读,使用 by lazy懒加载
    val num2 by lazy {
        println("打印就说明val加载了")
        MutableLiveData<Int>()
    }
}

val model = Demo()   //什么也不会打印

6.2 幕后属性

        类似于 Java 类中私有化成员变量并提供公共访问的写法(封装)。

        如果一个类有两个概念上相同的属性,一个是公共 API 的一部分,另一个是实现细节,那么使用下划线作为私有属性名称的前缀。

        幕后属性 _num 是私有的,它用来存储一个MutableLivedata,再定义一个 num 用来提供外部对 _num 的访问。

private var _num = mutableLiveData<Int>(0)   //幕后属性用于私有化属性
val num = _nam as LiveData<Int>    //val让对象只读,as强转让对象内数据不可变
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值