Kotlin Backing Field

备用字段?
幕后字段?
支持字段?
后端域变量?
翻译这么多种,其实都是一个意思 Backing Field

找到一些关于他的描述:

  • Kotlin中的类不能有field。但是,有时在使用自定义访问器时必须有一个 backing field 。为此,Kotlin提供了一个自动backing field,可以使用 field 标识符来访问。

  • Backing field is an autogenerated field for any property which can only be used inside the accessors(getter or setter) and will be present only if it uses the default implementation of at least one of the accessors, or if a custom accessor references it through the field identifier.This backing field is used to avoid the recursive call of an accessor which ultimately prevents the StackOverflowError.

  • 幕后字段是一个自动生成的字段,它仅仅可以被用在拥有至少一个默认访问器 (getter、setter) 、或者在自定义访问器中通过 field 标识符修饰的属性中。幕后字段可以避免访问器的自递归而导致程序崩溃的 StackOverflowError 异常。

那么这里有一些关于field的特点:

  • field标识符只允许在属性的访问器函数内使用。
  • 如果你显式地引用或者使用默认的访问器实现,编译器会为属性生成field。
  • 如果你提供了一个自定义的访问器实现并且没有使用field,将不会生成field。
  • 访问属性的方式不依赖于它是否含有field。

在setter函数体中,使用了特殊标记符field来访问字段的值
在getter中,只能读取该值。
在setter中,既能读取也能修改它。

//使用field关键字
public var fieldProp = ""
    get() = field
    set(value) {
        field = value
    }
//自定义不使用field 不生成:
val isEmpty: Boolean
    get() = this.size == 0
//默认访问器 生成:
val Foo.bar = 1

如果在类中定义一个成员变量,kotlin将自动生成默认的setter/getter方法。
kotlin声明get/set的方式为

  var name: String? = null
        set(value) {
            field = value
        }
        get() = field

这里使用了field,如果不使用field,会这么写:

  var name: String? = null
        set(value) {
            name = value
        }
        get() = name

但是这么写,对这个属性进行赋值并取值时出现了一些问题:


图片1.png

在setter方法中对属性进行赋值和取值时,会调用自身,出现了递归调用。

这个时候field就可以解决这个问题了,backing field的作用域在当前属性的setter/getter方法中,以中间变量的形式,来解决了递归问题。

在拓展属性中

在拓展属性中,是没有field这个字段的


图片2.png

转换一下写法


图片3.png

按照正常的写法,这里报错。说明拓展属性没有field。
如果把这个拓展常量变成属性,会提示属性必须初始化:


图片4.png

那我们直接初始化试试


图片5.png

直接赋值进行初始化又报错了: 拓展属性不能初始化,因为他没有支持字段 (backing field)。
为什么拓展属性不能初始化?这个问题可以转换为 为什么拓展属性没有支持字段?

官方说:

实际上,扩展并不会真正地往类中插入成员变量。因此,我们没有一个有效的方式让一个扩展属性拥有backing field,这就是扩展属性不允许被初始化的原因。

那么拓展属性到底是什么?
Kotlin 可以对一个类的属性和方法进行扩展,且不需要继承或使用装饰器模式。扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。

因为不会对类的代码本身造成任何影响,所以扩展不会真正的往类中插入成员变量。

field的作用域是当前属性的访问器,而当前属性是与某个类的实例对应的,所以类中并没有这个属性,也就没有一个有效的方式让这个属性拥有backing field了。

  • 有field和没有field的属性有什么区别?

访问属性的方式不依赖于它是否含有field,如果你显式地引用或者使用默认的访问器实现,编译器会为属性生成field。如果你提供了一个自定义的访问器实现并且没有使用field,支持字段将不会被呈现出来。

有时候不需要修改访问器的默认实现,但是需要修改它的可见性:

class LengthCounter {
  var counter: Int = 0
    private set //这个属性不能在所在类外部被修改
  fun addWord(word: String) {
    counter += word.length
  }
}

在接口中

在接口中,可以包含具有setter/getter的属性,只要他们没有引用一个backing field,
backing field需要在接口中存储状态,而这是不被允许的


图片6.png

图片7.png
支持属性:功能与backing field相似,能达到相同的效果:
private var _table: Map? = null
public val table: Map
    get() {
        if (_table == null) {
            _table = HashMap() // 参数类型是自动推导
        }
        return _table ?: throw AssertionError("Set to null by another thread")
    }
参考

https://medium.com/@agrawalsuneet/backing-field-in-kotlin-bd9c2d5b6da5
https://juejin.im/post/5a79c053f265da4e6e2bad37
https://blog.csdn.net/Strange_Monkey/article/details/82707242

你可能感兴趣的:(Kotlin Backing Field)