大家好,我是William李梓峰,歡迎加入我的Kotlin學習之旅。
今天是我學習 Kotlin 的第九天,學習內容是 Properties and Fields - 屬性和字段
之前,我用筆名小李君,后來發現,現在都2017年了,筆名太土了,還是直接真名吧,說不定以后會火了呢,就醬紫。
官方文檔:
Properties and Fields - 屬性和字段
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.
類在 Kotlin 的世界里面可以有屬性。
用 var 來聲明可變的屬性,用 val 來聲明不可變的屬性。
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:
怎么調取屬性呢?直接像 JavaScript 那樣直取就行了。不用通過 getter 或 setter :
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
}
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).
初始化代碼,getter 和 setter 都是可寫可不寫。屬性的類型也是可選的,只要它能夠通過初始化代碼推斷出類型
(或從 getter 的返回類型中推斷,就像下面演示的那樣)。
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 沒 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:
我們可以寫一個自定義的訪問器,就像函數那樣子,直接寫在屬性的下面。這里有個自定義 getter 訪問器:
val isEmpty: Boolean
get() = this.size == 0 // getter 寫的是函數表達式,返回布爾值
A custom setter looks like this:
一個自定義的 setter 可以這么寫:
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.
按照慣例,setter 的形參名都是 value,但是你可以寫其他的名字(廢話)。
Since Kotlin 1.1, you can omit the property type if it can be inferred from the getter:
自從 Kotlin 1.1 開始,你可以忽略屬性的類型,只要它可以從 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:
如果你需要改變訪問器的可訪問性或注解的修飾,是并不需要改變原來 setter 或 getter 的默認實現的。
你可以直接定義訪問器而不用寫它的 “身體”(別誤會,明明是大括號的代碼實現,這純粹是文化差異)。
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
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:
Kotlin 的類不可以擁有字段(只可以有屬性,明明就是一個東西)。但是,有時候它還可以有個備用字段為自定義訪問器所用。Kotlin 提供了一個自動備用字段,這種字段可以用字段的識別碼去訪問。(什么是識別碼。。。)
var counter = 0 // the initializer value is written directly to the backing field
set(value) {
if (value >= 0) field = value // field 就是代表 counter 本身
}
The field
identifier can only be used in the accessors of the property.
field 只可以用于 setter 或 getter,代表當前字段,類似 this 代表當前對象一樣。
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.
一個備用字段會生成出來,用于屬性的訪問器默認實現,或自定義訪問器通過 field 識別碼來調取。
For example, in the following case there will be no backing field:
例如,在這個下面這個例子中,就沒有備用字段:
val isEmpty: Boolean
get() = this.size == 0 // 如果是 get() = field 應該就是默認實現了
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.
反正呢,別管這么多了,在 Java 的世界里面,private field 都是通過 public setter getter 訪問的,這是最佳實踐,同時也是約定俗成的老規矩。Kotlin 只是簡化了這個過程。聲明一個屬性,就會同時生成對應的 setter getter(訪問器),你可以直接重寫任意訪問器,可以在訪問器里面通過 field 來訪問當前字段。真是夠啰里啰嗦的。
Compile-Time Constants - 編譯時常量
Properties the value of which is known at compile time can be marked as compile time constants using the const
modifier.
屬性可以用 const 標記為編譯時常量。
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
- 頂級對象或頂級成員
- 用 String 類型或一個基本類型初始化
- 沒有自定義 getter
Such properties can be used in annotations:
這種屬性可以直接用在注解上,爽了:
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
Late-Initialized Properties - 延遲初始化屬性
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.
例如,屬性可以通過依賴注入來初始化(Spring 要亂入),或者在單元測試的預備方法內初始化。在這種場景下,你不可以在構造器里面完成屬性的初始化(因為調用完了構造器,屬性還必須是空的,因為要注入啊,但不想直接寫問號標明這些屬性是可以為空的,明明就不想為空啊),但你卻還想要在類體中調用屬性時避免空值檢查。這咋辦?
To handle this case, you can mark the property with the lateinit
modifier:
為了處理這種場景,你可以標記這種屬性為 “lateinit”,延遲初始化登場!!
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // dereference directly
}
}
The modifier can only 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. The type of the property must be non-null, and it must not be a primitive type.
這個修飾器只可以用在 var 屬性上,而且也僅在屬性沒有自定義 getter 或 setter 的前提下使用。這種屬性還必須是不為空的,且不是基本類型。
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.
訪問一個延遲初始化的屬性,只要它還沒初始化,就會拋出一個特定的異常,這種異常會告訴你這個屬性還沒被初始化。(感覺這里會有坑咯)
Overriding Properties - 復寫屬性
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.
但是,用自定義 getter 和 setter 可以實現任何關于屬性的行為。(意思是說,可以通過重寫 getter 返回某個值,而不是屬性自己的值。)
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.
在某些地方,存在一些關于屬性使用的通用模式。例如,延遲值,從 map 的 key 中取值,從數據庫中取值,從監聽器取值,等等。。。
Such common behaviours can be implemented as libraries using delegated properties.
這些通用行為可以通過代理屬性的機制來實現的。傳送門
今天就寫到這了,完。