大家好,我是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.
这些通用行为可以通过代理属性的机制来实现的。传送门
今天就写到这了,完。