重学Kotlin

属性和域

2017-07-09  本文已影响0人  Mobile_Joy

属性和域

什么是属性?
实际是指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. 属性不能是基本类型
上一篇下一篇

猜你喜欢

热点阅读