Kotlin-2.2-属性和字段

翻译自:官方权威文档2.2-Properties and Fields
增加部分本人补充的知识点和实例。
翻译中有错误和纰漏,欢迎指正!

1-属性和字段(Field)

1-声明属性

在Kotlin中的类可以有属性。
可以使用var关键字声明可变属性,或者使用关键字val声明只读属性:

class Address {
    var name: String = ... //可变属性
    var street: String = ...
    var city: String = ...
    var state: String? = ...
    var zip: String = ...
}

为了使用属性,我们简单地通过名字去调用它,好像它是Java中的字段(fields):

fun copyAddress(address: Address): Address {
    val result = Address() // Kotlin中没有new关键字
    result.name = address.name // 调用对象的属性(等同成员变量)
    result.street = address.street
    // ...
    return result
}

2-Getters and Setters

声明一个属性的完整语法如下:

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

初始化、getter和setter都是可选的。属性类型在其能被初始化代码推测出来的情况下,也是可选的(或者可以从getter的返回类型推测出来时,如下面的代码):

var allByDefault: Int? // 错误: 需要显式构造器, 提供了默认的getter和setter
var initialized = 1 // 具有Int类型, 默认的getter和setter

只读属性的完整语法只和可变属性的声明有两方面不同:用val代替了var,和不允许有setter方法:

val simple: Int? // 有类型Int,默认的getter, 必须在构造器中初始化
val inferredType = 1 // 有类型Int和默认的getter

我们可以写自定义访问器(getter/setter),非常像一般的函数,就在属性声明的内部。自定义getter的实例如下:

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

自定义setter如下:

var stringRepresentation: String
    get() = this.toString()
    set(value) {
        setDataFromString(value) // 传入String并且分配到其他属性中
    }

习惯上,setter的参数名是value,但是你可以选择一个你喜欢的名称。

自从Kotlin 1.1,在能通过getter推断出类型的情况下,可以省略属性类型:

val isEmpty get() = this.size == 0  // 具有类型Boolean

如果你需要改变访问器的可见性或者对其进行注释,不需要改变默认的实现,你可以定义访问器而不需要定义主体部分:

var setterVisibility: String = "abc"
    private set // setter是私有的,并且有默认实现

var setterWithAnnotation: Any? = null
    @Inject set // 使用inject注释setter

1-支持字段(Backing Fields)

Kotlin中的类没有字段(fields).但是,有时在使用自定义访问器时,支持字段是有必要的。为了这个目的,Kotlin提供一种自动的支持字段,其可以通过field标识符去访问:

var counter = 0 // 初始化值直接赋予了 支持字段(backing field)
    set(value) {
        if (value >= 0) field = value
    }

这里的backing field到底是做什么的?
这里field = value是将值赋予counter。如果使用代码if (value >= 0) this.counter = value会导致无穷无尽地调用set()方法,因为这里在java中类似于执行if (value >= 0) set(value)。因此就需要backing filed来解决这种情况。

标识符field仅仅可以使用在属性的访问器中。

如果属性使用了最少一个访问器的默认实现,或者自定义访问器通过标识符field引用它,支持字段backing field将会产生。

例如,在下列代码中就不会有backing field:

val isEmpty: Boolean
    get() = this.size == 0 //val只有getter

2-支持属性(Backing Properties)

如果你想要做一些不会符合“隐式backing field”计划的事情,你也可以回到支持属性上(backing property):

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")
    }

在所有方面,这就和在Java中一样,访问私有属性通过默认getter和setter方法被优化,以至于没有产生函数调用开销。

3-编译时 常量

属性的值若要在编译时可获得,可使用修饰符const来标记,这样就成为了编译时常量

这样的属性需要充分满足下列需求:

  • “对象”的顶层或者成员。
  • 用类型String的值初始化或者用传统类型初始化。
  • 没有自定义getter

这种属性可以用在注解中:

const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated" //常量

@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... } //用于注解:deprecated

4-延迟初始化属性和变量

通常,用非空类型初始化的属性,必须在构造器中初始化。
但是,这样经常很不方便。例如属性可以通过依赖注入去初始化,或者在单元测试的启动方法中初始化。在这种情况,你不可以在构造器中产生一个非空的初始化,但是你仍然想要在类主体中引用该属性时避免null checks。

为了解决这种情况,你可以使用修饰符lateinit标记这个属性:

public class MyTest {
    lateinit var subject: TestSubject //延迟初始化

    @SetUp fun setup() {
        subject = TestSubject()
    }

    @Test fun test() {
        subject.method()  // 直接引用
    }
}

该修饰符可以用在类主体内的var属性声明中(不能再首要构造器中,并且仅当该属性没有自定义访问器时),并且自从Kotlin 1.2,可用于顶层属性和局部变量。该属性或者变量的类型必须非空,它一定不能是传统类型。

访问一个还没有来得及被初始化的lateinit属性,会抛出一个特殊的异常:清晰指明该属性在没有被初始化前就被访问。

1-确定lateinit变量是否被初始化(since 1.2)

为了确定一个 lateinit var变量是否已经被初始化,可以在属性的引用上调用.isInitialized(例如):

if (foo::bar.isInitialized) {
    println(foo.bar)
}

这种确定方式只能用于词汇访问的属性,例如,以相同类型声明,或者外部类型声明的属性,以及在同一个文件顶层。

This check is only available for the properties that are lexically accessible, i.e. declared in the same type or in one of
the outer types, or at top level in the same file.

5-重载属性

参考 重载属性

6-代理属性

代理属性是最常用的一种属性:能简单的从后台区域读取或者写入到后台区域的属性。另一方面,代理属性具有自定义访问器,并且能实现属性的任何行为。此外,代理属性具有明确的属性如何工作的通用模式。一些例子:lazy数值,从给定key的map中读取数据,访问一个数据库。

这些更通用的行为可以作为库来实现delegated properties

翻译的不准确:

The most common kind of properties simply reads from (and maybe writes to) a backing field.
On the other hand, with custom getters and setters one can implement any behaviour of a property.
Somewhere in between, there are certain common patterns of how a property may work. A few examples: lazy values,
reading from a map by a given key, accessing a database, notifying listener on access, etc.
Such common behaviours can be implemented as libraries using delegated properties.


官方文档英文原文参考如下:

2-Properties and Fields

1-Declaring Properties

Classes in Kotlin can have properties.
These can be declared as mutable, using the var{: .keyword } keyword or read-only using the val{: .keyword } keyword.

class Address {
    var name: String = ...
    var street: String = ...
    var city: String = ...
    var state: String? = ...
    var zip: String = ...
}

To use a property, we simply refer to it by name, as if it were a field in Java:

fun copyAddress(address: Address): Address {
    val result = Address() // there's no 'new' keyword in Kotlin
    result.name = address.name // accessors are called
    result.street = address.street
    // ...
    return result
}

2-Getters and Setters

The full syntax for declaring a property is

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

The initializer, getter and setter are optional. Property type is optional if it can be inferred from the initializer
(or from the getter return type, as shown below).

Examples:

var allByDefault: Int? // error: explicit initializer required, default getter and setter implied
var initialized = 1 // has type Int, default getter and setter

The full syntax of a read-only property declaration differs from a mutable one in two ways: it starts with val instead of var and does not allow a setter:

val simple: Int? // has type Int, default getter, must be initialized in constructor
val inferredType = 1 // has type Int and a default getter

We can write custom accessors, very much like ordinary functions, right inside a property declaration. Here’s an example of a custom getter:

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

A custom setter looks like this:

var stringRepresentation: String
    get() = this.toString()
    set(value) {
        setDataFromString(value) // parses the string and assigns values to other properties
    }

By convention, the name of the setter parameter is value, but you can choose a different name if you prefer.

Since Kotlin 1.1, you can omit the property type if it can be inferred from the getter:

val isEmpty get() = this.size == 0  // has type Boolean

If you need to change the visibility of an accessor or to annotate it, but don’t need to change the default implementation,
you can define the accessor without defining its body:

var setterVisibility: String = "abc"
    private set // the setter is private and has the default implementation

var setterWithAnnotation: Any? = null
    @Inject set // annotate the setter with Inject

1-Backing Fields

Classes in Kotlin cannot have fields. However, sometimes it is necessary to have a backing field when using custom accessors. For these purposes, Kotlin provides
an automatic backing field which can be accessed using the field identifier:

var counter = 0 // the initializer value is written directly to the backing field
    set(value) {
        if (value >= 0) field = value
    }

The field identifier can only be used in the accessors of the property.

A backing field will be generated for a property if it uses the default implementation of at least one of the accessors, or if a custom accessor references it through the field identifier.

For example, in the following case there will be no backing field:

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

2-Backing Properties

If you want to do something that does not fit into this “implicit backing field” scheme, you can always fall back to having a backing property:

private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
    get() {
        if (_table == null) {
            _table = HashMap() // Type parameters are inferred
        }
        return _table ?: throw AssertionError("Set to null by another thread")
    }

In all respects, this is just the same as in Java since access to private properties with default getters and setters is optimized so that no function call overhead is introduced.

3-Compile-Time Constants

Properties the value of which is known at compile time can be marked as compile time constants using the const modifier.
Such properties need to fulfil the following requirements:

  • Top-level or member of an object
  • Initialized with a value of type String or a primitive type
  • No custom getter

Such properties can be used in annotations:

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

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

4-Late-Initialized Properties and Variables

Normally, properties declared as having a non-null type must be initialized in the constructor.
However, fairly often this is not convenient. For example, properties can be initialized through dependency injection,
or in the setup method of a unit test. In this case, you cannot supply a non-null initializer in the constructor,
but you still want to avoid null checks when referencing the property inside the body of a class.

To handle this case, you can mark the property with the lateinit modifier:

public class MyTest {
    lateinit var subject: TestSubject

    @SetUp fun setup() {
        subject = TestSubject()
    }

    @Test fun test() {
        subject.method()  // dereference directly
    }
}

The modifier can be used on var properties declared inside the body of a class (not in the primary constructor, and only
when the property does not have a custom getter or setter) and, since Kotlin 1.2, for top-level properties and
local variables. The type of the property or variable must be non-null, and it must not be a primitive type.

Accessing a lateinit property before it has been initialized throws a special exception that clearly identifies the property
being accessed and the fact that it hasn’t been initialized.

1-Checking whether a lateinit var is initialized (since 1.2)

To check whether a lateinit var has already been initialized, use .isInitialized on
the reference to that property:

if (foo::bar.isInitialized) {
    println(foo.bar)
}

This check is only available for the properties that are lexically accessible, i.e. declared in the same type or in one of
the outer types, or at top level in the same file.

5-Overriding Properties

See Overriding Properties

6-Delegated Properties

The most common kind of properties simply reads from (and maybe writes to) a backing field.
On the other hand, with custom getters and setters one can implement any behaviour of a property.
Somewhere in between, there are certain common patterns of how a property may work. A few examples: lazy values,
reading from a map by a given key, accessing a database, notifying listener on access, etc.

Such common behaviours can be implemented as libraries using delegated properties.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值