Kotlin 属性与字段
声明属性
Kotlin 类中的属性既可以用关键字 var
声明为可变的,也可以用关键字 val
声明为只读的。
class Address {
var name: String = "Ricky,Lee";
var street: String = "Panjiayuan"
var city: String = "BeiJing"
var zip: String = "100000"
}
如果要使用一个属性,只要用名称引用它即可:
fun copyAddress(address: Address): Address {
val copyResult = Address()
copyResult.name = address.name
copyResult.street = address.street
copyResult.city = address.city
copyResult.zip = address.zip
return copyResult
}
Getters 与 Setters
声明一个属性的完整语法是:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
其初始器(initializer)、getter 和 setter 都是可选的。属性类型如果可以从初始器 (或者从其 getter 返回值)中推断出来,也可以省略。
只读属性的语法和可变的属性的语法有两方面的不同:
- 只读属性的用
val
开始代替var
- 只读属性不允许 setter
如果我们不显式的添加 setter/getter ,Kotlin 会默认添加,如下就是默认的 setter/getter:
class Person {
var name: String? = null
//以下两种getter都可以
// get() = field
get() {
return field
}
set(value) {
field = value
}
val age: Int? = null
// get() = field
get() {
return field
}
}
虽然 Kotlin 不提倡在代码中显式的添加默认的 setter/getter ,但是我们可以根据需求自定义 setter/getter 。
幕后字段(Backing Fields)
在上面代码中,我们用到一个关键字 field
,它一般只会在 setter/getter 中使用。它代表就是当前的变量。
class Person {
// 在初始化器中给变量赋值,无法触发 setter
var name: String? = null
// value 代表的是当前 name 的值
set(value) {
// field 代表的就是 name
field = if (value == "LiTeng") {
"Ricky"
} else {
"Ricky.Lee"
}
}
val age: Int? = null
get() {
return if (name == "Ricky") {
20
} else {
field
}
}
}
fun main() {
val person = Person()
// 动态的给变量赋值,会触发 setter
person.name = "LiTeng"
println("name:${person.name}")
println("age:${person.age}")
}
上面的代码输出:
name:Ricky
age:20
通过上面的代码,我们可以看出,通过setter我们可以在变量赋值的时候改变变量的值。使用 val
修饰的变量并不是不可变的,我们可以通过自定义的 getter 动态改变其值。
编译期常量
在编译器在编译的时候就知道这个变量在实际运行时的具体值,这个变量就叫做编译期常量。我们一般使用 const
修饰编译器常量,其后面必须跟 val
。
编译期常量需要满足以下条件:
- 位于顶层或者使用
object
声明或companion object
的一个成员 - 必须是基本类型值或者String
- 没有自定义 getter
常量的写法
1.使用Kotlin 的顶层声明(top-level declaration)设计
所谓的顶层声明,就是把属性和函数声明直接写在 kt 文件中,而不是写在 class 中。
package vip.liteng.kotlin.sample.classes
const val TOP_LEVEL = "top"
fun topLevelFun(){
/* .... */
}
这样写的属性和函数,不属于任何 class
,而是直接属于 package
,它和静态变量、静态函数一样是全局的。如果我们要使用这些属性和函数,只要我们导入对应的属性和函数,就可以在项目中的任意地方直接使用属性名或者函数名就可以直接调用。
import vip.liteng.kotlin.sample.classes.TOP_LEVEL
import vip.liteng.kotlin.sample.classes.topLevelFun
fun main() {
println(TOP_LEVEL)
topLevelFun()
}
2.使用 object
声明或 companion object
使用 object
在 Kotlin 就是其本意,对象的意思。而在代码中它的意思是创建一个类,并且创建一个这个类的对象 。如下代码:
object Car {
// object 修饰的类中的变量默认为 编译期常量
const val name: String = "黑色"
}
fun main() {
// 我们可以使用 类名.属性 直接调用
Car.name
}
其实上面的代码就是一种饿汉式的单例形式。在 Kotlin 中只需使用 object
就可以很简单的创建一个单例,并且是线程安全的。
我们将上面的代码转换为 java 代码:
public final class Car {
// 使用 const val 声明的常量,直接被转换为 static final 的常量
@NotNull
public static final String name = "黑色";
public static final Car INSTANCE;
private Car() {
}
static {
// 在静态代码块中直接创建了对象。
Car var0 = new Car();
INSTANCE = var0;
}
}
在 Java 更直观的看到,在静态代码块中直接创建了对象,并且我们使用 const val
声明的常量,直接被转换为 static final
的常量。
同样的我们使用伴生对象 companion object
跟上面的类似,但是一个类只能有一个伴生对象;并且 伴生对象必须声明类内;不像 object
也可以在 顶层声明中使用。
class Car{
companion object A {
const val name: String = "黑色"
}
}
fun main() {
// 伴生对象可以像下面的方式通过 类名.伴生对象名.常量名 调用;但是其中伴生对象名是多余的,一般我们要忽略,直接使用下面的 类名.常量名 方式调用
Car.A.name
// 直接使用 类名.常量名 的方式调用
Car.name
}
延迟初始化属性与变量
有时候我们声明的变量,没有办法在声明的第一时间给它赋值,那么我们还可以使用 延迟初始化修饰符 lateinit
。
// 使用 lateinit 无需对变量在声明时进行初始化,可以在需要的时候进行初始化
lateinit var fileName: String
fun main() {
fileName = "/opt/home"
}
延迟初始化修饰符 lateinit
只能修饰在类体中的属性,并且修饰使用 var
变量。 在 Kotlin 1.2 只有 lateinit
也可以修饰 顶层变量 和 局部变量。该属性或变量必须为非空类型,并且不能是原生类型。
在初始化前访问一个 lateinit
属性会抛出 kotlin.UninitializedPropertyAccessException
。
比如:
lateinit var AB: String
fun main() {
println(AB)
}
上面的代码会抛出以下的异常:
Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property AB has not been initialized
at vip.liteng.kotlin.sample.classes.PropertyAndFieldKt.main(PropertyAndField.kt:81)
at vip.liteng.kotlin.sample.classes.PropertyAndFieldKt.main(PropertyAndField.kt)
检测一个 lateinit var 是否已初始化(自 1.2 起)
如果要检测 lateinit var
是否已经初始化,可以在该属性的引用上使用.isInitialized
:
lateinit var AB: String
fun main() {
if (::AB.isInitialized) {
println("AB 已经初始化")
}else{
println("AB 还没有初始化")
}
}
上面的方法只能对同一个类型内、位于其中一个外围类型中或者位于相同文件的顶层的属性。
总结:
- 使用
var
声明可变变量;使用val
声明只读变量; var
变量存在 getter/setter ;而val
变量只存在 getter,并且可以通过 getter 改变val
变量的值;- 要使用一个属性,只要用名称引用它即可;
- 常量使用
const val
修饰; - 常量可以在 top-level 声明;
- 常量可以在
object
和companion object
中声明;且默认为const val
类型; - 如果不想在声明的时候给变量赋值,可以使用
lateinit var
修饰; - 如果
lateinit var
修饰的变量没有初始化,在使用时户抛出异常