Kotlin-属性

声明属性

对于类而言,属性必然是不可缺少的。在Kotlin中,属性值由var和val关键字声明,其中,var声明的属性值为可变的,而val声明的属性值是只读属性,也就是说其值是不可变的。

class Person {
    var name = "张红"
    var age: Int = 10
    val sex: String = "M"
    private var mobile: String = "606066"
}
  1. 声明的属性值必须得初始化,属性必须初始化,否则编译报错.或者将此属性用abstract修饰符修饰。在abstract修饰的属性值,即使不用初始化,必须声明其数据类型,并在其子类初始化。

    abstract class Person {
        abstract var name: String
        var age: Int = 10
        val sex: String = "M"
        private var mobile: String = "606066"
    }
    
  2. 默认为被public修饰符修饰,也就意味着其他类可以通过其类任意调用。如果禁止其他类访问其,本身可以使用private修饰符修饰。
  3. 编译器会自动生成getter和setter方法。所以上面的属性编译器默认添加了getter和setter方法。
  4. 访问其某个类的属性值,使用”.”操作符,例如,当我们访问Person的name的属性值时,只需 person.name即可。

Getter和Setter

上面说到了,声明属性时,编辑器会自动生成getter和setter方法,这其实偷懒的方式,并不是完整的属性声明。
完整的属性的声明如下:

var <propertyName>: <PropertyType> [= <property_initializer>]
[<getter>]
[<setter>]

其中initializer, getter 和 setter都是可选的。var是允许有getter 和 setter方法,如果变量是val声明的,它类似于Java中的final,所以如果以val声明就不允许有setter方法。如果属性值的数据类型可以通过编译器自动推断,或者在getter和setter方法中并没有对属性做特殊处理,这些方法都可以省略。

例如:

var allByDefault: Int? // 错误: 需要明确指定初始化器, 此处会隐含地使用默认的取值方法和设值方法
var initialized = 1 // 属性类型为 Int, 使用默认的取值方法和设值方法

对于属性,如果你想改变访问的可见性或者是对其进行注解,但是又不想改变它的默认实现,那么你就可以定义set和get但不进行实现。

var setterVisibility: String = "abc"
private set // 设值方法的可见度为 private, 并使用默认实现
var setterWithAnnotation: Any? = null
@Inject set // 对设值方法添加 Inject 注解

自定义Getter和Setter

Getter和Setter方法,可以根据实际情况自定义。但,var和val又有点不同,如果var是允许有getter和setter方法,如果是val只有getter而没有setter方法,因为val的值是不可变的。

class Person {

    var lastName: String = "zhang"
    get() = field.toUpperCase()
    set

    var no: Int = 100
    get() = field
    set(value) {
        if (value < 10) {
            field = value
        } else {
            field = -1
        }
    }

    var heiht: Float = 145.4f
    private set
}

// Test
fun main(args: Array<String>) {
    var person: Person = Person()

    person.lastName = "wang"

    println("lastName:${person.lastName}")

    person.no = 9
    println("no:${person.no}")

    person.no = 20
    println("no:${person.no}")

}

// Log
lastName:WANG
no:9
no:-1

在Person类中,声明了两个可变属性lastName和no。对于lastName而言,修改了其getter方法,其返回值是将字符串中的字母大写,而set并未做实现,在测试类中,对lastName重新赋值为“wang”,其字母全部为小写,但在打印出的Log中,lastName的值全部为大写。而对于no属性,修改了setter方法,当预设置的值小于10时,将其值赋值给no,如果设置的大于10时,将no赋值为-1。在测试代码中,分别对no赋值9和20,而在实际打印中分别打印的是9和-1。自定义getter和setter的用法,不言而喻,想想也就明白了。

在自定义的getter和setter的方法中,用到了“field”,这是个什么东东呢?刚开始看到的,很不是理解吧,后续再说。


注:

  1. getter方法的可见性与属性的可见性一致。假如声明一个public变量,将其的getter方法用其他修饰符修饰,会报错。
    这里写图片描述

  2. setter方法可以自定义修饰符,在实例代码中就可以看到,此时setter的修饰符不一定与属性的修饰符一致,其使用范围由修饰符决定,并不一定与属性的适用范围一致。例如,将属性no的set方法用private修饰,在此类外部并不能对此变量赋值。
    这里写图片描述

后端变量(Backing Fields)

Backing Fields,看大神的翻译文档为,后端域变量,先不管怎么翻译的,先看看怎么用的吧。由于Kotlin中,并不允许使用局部变量,使用编辑器的自定义的访问器时,就要用到刚才提到的“field”了,其实际上Kotlin提供的一种Backing Fields,由field标识符来访问。

    var no: Int = 100
    get() = field
    set(value) {
        if (value < 10) {
            field = value
        } else {
            field = -1
        }
    }

编译器会检查属性访问器的函数体, 如果使用了后端域变量(或者,如果没有指定访问器的函数体, 使用了默认实现), 编译器就会生成一个后端域变量, 否则, 就不会生成后端域变量。

val isEmpty: Boolean
get() = this.size == 0

这是官方文档的一个例子,在访问属性值isEmpty时,并不会生成后端变量,因为其值是由其实例的长度决定的,并不需要一个后端域变量进行过度。其实,我是这么认为的,所谓的后端域变量其实起到一个过度的作用,方便与开发者进行相关的操作,而不用调用其本身了。

注:

field 标识符只允许在属性的访问器函数内使用.

后端属性(Backing Property)

后端属性,可以看作是后端变量(Backing Fields)的变种吧,其实际上也是隐含试的对属性值的初始化声明,避免了空指针。

private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null)
_table = HashMap() // 类型参数可以自动推断得到, 不必指定
return _table ?: throw AssertionError("Set to null by another thread")
}

个人觉得,不管是后端变量或者后端属性,都是Kotlin所特有的且针对于空指针的一种解决方案,与Java的解决方式一致,不过,起了个名字,显得高端大气上档次了。不过, 后端属性的存在,对于属性值的访问被优化,如果为设定属性值,可以通过读写后端属性,避免了不必要的麻烦,还是非常有用的,可以把这个方案用于Java开发。

编译期常数值

如果在编译期间,属性值就能被确定, 该类属性值使用const 修饰符, 将属性标记为_编译期常数值(compile timeconstants). 这类属性必须满足以下所有条件:

  • 必须是顶级属性, 或者是一个object的成员
  • 值被初始化为 String 类型, 或基本类型(primitive type)
  • 不存在自定义的取值方法

这类属性可以用在注解内:

const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }

延迟初始化属性

都知道的是,在类内声明的属性必须初始化,如果设置非NULL的属性,应该将此属性在构造器内进行初始化。假如想在类内声明一个NULL属性,在需要时再进行初始化,与Kotlin的规则是相背的,此时我们可以声明一个属性并延迟其初始化,此属性用lateinit修饰符修饰。

class MyInfo {
    lateinit var person: Person

    fun initData() {
        person = Person()
    }
}

// Test
fun main(args: Array<String>) {
    val myInfo: MyInfo = MyInfo()

    myInfo.initData()
    myInfo.person.doSwim()
}

// Log  
do Swim

在类MyInfo中,声明了以属性person,其被lateinit修饰符修饰,person并没有初始化,默认为NULL。在使用其之前,调用了initData()方法,对person进行了初始化。


注:

  • 被声明延迟初始化的属性,必须在使用以前调用其初始化方法,否则会报异常。

    fun main(args: Array<String>) {
        val myInfo: MyInfo = MyInfo()
    
        myInfo.person.doSwim()
    }、
    

这里写图片描述
- lateinit修饰符只能用于 var 属性, 而且只能是声明在类主体部分之内的属性(不可以是主构造器中声明的属性)

  • 此类属性不能有自定义的取值方法和设值方法.

  • 属性类型必须是非 null 的, 而且不能是基本类型.

参考资料

  1. Kotlin阅读笔记(5)-属性和字段
  2. Kotlin 变量和属性
  3. Kotlin学习笔记——属性和字段
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值