Kotlin属性和变量-2.2-Properties and Fields

来源:互联网 发布:github源码在微信上 编辑:程序博客网 时间:2024/05/22 11:33

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

  • 1-属性和变量
    • 1-声明属性
    • 2-Getters and Setters
      • 1-支持字段Backing Fields
      • 2-支持属性Backing Properties
    • 3-编译时 常量
    • 4-延迟初始化属性和变量
      • 1-Checking whether a lateinit var is initialized since 12
    • 4-Overriding Properties
    • 5-Delegated Properties
  • 2-Properties and Fields
    • 1-Declaring Properties
    • 2-Getters and Setters
      • 1-Backing Fields
      • 2-Backing Properties
    • 3-Compile-Time Constants
    • 4-Late-Initialized Properties and Variables
      • 1-Checking whether a lateinit var is initialized since 12
    • 4-Overriding Properties
    • 5-Delegated Properties

1-属性和变量

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和settervar 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>? = nullpublic 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,可用于顶层属性和局部变量。该属性或者变量的类型必须非空,它一定不能是传统类型。

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.

4-Overriding Properties

See Overriding Properties

5-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 impliedvar 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 constructorval 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 implementationvar 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>? = nullpublic 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.

4-Overriding Properties

See Overriding Properties

5-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.

原创粉丝点击