属性和域

作者: Mobile_Joy | 来源:发表于2017-07-09 23:34 被阅读0次

    属性和域

    什么是属性?
    实际是指getter和setter方法,虽然和字段是两个概念,姑且可以理解为有getter和setter方法的字段。
    如何声明?
    完整语法:

    var 属性名: <属性类型> [= <初始化器>]
        [<getter>]
        [<setter>]
    

    其中的初始化器、getter和setter方法是可选的。如果没有自定义getter和setter的实现,就会使用默认的。
    以下两种情况属性类型也可省略:

    1. 可以通过初始化器推断出来
    2. 被声明的属性会覆盖基类中的属性,并且通过基类中属性的类型推动出来

    两种属性:
    只读属性:val关键字声明
    可变属性:var关键字声明
    访问属性:
    访问属性使用点操作符,对象名.属性名,如:

    var person = Person()
    person.name = "张三"
    

    和oc一样,点操作符内部的实现也是调用getter或setter访问器。并且在Kotlin中访问Java的属性时,仍然可以用点语法访问Java对象中的属性。

    如果要修改访问器的可见性或添加注解,但又不需要修改默认实现,你可以重新定义方法,但不定义它的实现,这样仍然是使用默认实现。如:

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

    属性的后端域变量(Backing Field)

    Kotlin类不能拥有域变量(也就是Java中的成员变量),但是使用访问器时又需要这种域变量,所以Kotlin提供了后端域变量,可以用field标识符来访问。
    如:

    var counter = 0 // 初始化给定的值将直接写入后端域变量中
        set(value) {
            if (value >= 0) field = value
        }
      }
    

    field就代表编译器自动生成的后端域变量,并且只允许在属性访问器中使用

    什么情况下会生成后端域变量?

    1. 访问器中任一个使用默认实现
    2. 自定义的访问器中通过field关键字访问属性

    下面这种情况下就不会生成后端域变量:

    val isEmpty: Boolean
        get() = this.size() == 0 //自定义访问器没有使用field关键字访问属性
    

    为什么不用this关键字访问属性呢?
    试想下,this代表当前对象,而上面说过通过点操作符,访问属性时,实际上是调用了访问器,在访问器内部实现中再调用访问器就会出现无限递归。所以,通过Backing Filed可以避免这个问题。

    后端属性(Backing Property)

    如果你的操作是想访问属性内部的属性,或者集合里面的元素,而不是属性本身,那么你可以声明这个属性为private的(后端属性),然后定义个public的属性并自定义其访问器,在自定义的访问器内部访问后端属性。如:

    private var _table: Map<String, Int>? = null
    public val table: Map<String, Int>
        get() {
            // 这里的操作不希望属于 backing filed,
            if (_table == null) {
                _table = HashMap() 
            }
            return _table ?: throw AssertionError("Set to null by another thread")
        }
    

    其实也没有什么神秘的,其实就像Java中我们通常写的,在访问私有属性时,在自定义访问器中添加些限制,避免取到或设置非法的值一个道理。只是在Kotlin中取了个高大上的名字。

    编译器常数值

    值在编译期就能确定的属性,用const关键字修饰。满足以下条件的属性可以标记为编译器常数值:

    1. 必须是顶级属性或是object的成员
    2. 值必须是String或基本类型
    3. 不能自定义取值方法

    如:

    const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
    

    编译器常数值可以用在注解内:

    @Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
    

    延迟初始化属性

    通常属性被声明为非null类型就必须就地初始化。但是,这种限制在很多情况下是不方便的,比如,声明的属性通过依赖注入的方式来初始化。这种情况下,你就可以使用lateinit关键字来修饰声明的属性。
    如:

    public class MyTest {
        lateinit var subject: TestSubject
    
        @SetUp fun setup() {
            subject = TestSubject()
        }
    
        @Test fun test() {
            subject.method()  // 直接访问属性
        }
    }
    

    使用限制:

    1. 只能用于var属性
    2. 只能用于类体内声明的属性
    3. 属性不能有自定义访问器
    4. 属性必须是非null
    5. 属性不能是基本类型

    相关文章

      网友评论

        本文标题:属性和域

        本文链接:https://www.haomeiwen.com/subject/izgehxtx.html