本系列文章来学习 Kotlin 和 Anko 插件 通过 Kotlin 开发一个 Android 项目。
Kotlin-Anko学习(1) Kotlin、Anko 介绍
Kotlin-Anko学习(2) Kotlin 语法基础-基本类型
Kotlin-Anko学习(3) Kotlin 语法基础-关键字 package、Import、if、when、for、while、return、break、continue
Kotlin-Anko学习(4) Kotlin语法-类、继承、抽象类
Kotlin-Anko学习(5) Kotlin语法-属性、字段、接口
属性
Kotlin 中 属性的声明包括:关键字 val或var 、属性名、属性类型、初始器、访问器组成 如下:
var [: ] [= ]
[]
[]
//示例
var allByDefault:Int ?= null
get() = if(field ==null){
0
}else{
field
}
// set访问器只有在var 可变属性中存在 val 没有set访问器
set(value){
field = if(value==null){
0
}else{
value-2
}
}
以上示例是完整的属性声明,一般初始器(initializer)、访问器getter 和 setter 都是可选的。属性类型如果可以从初始器 、访问器中推断出来,也可以省略。总之遵循根据上下文推算出来的就可以不去特意声明。这也是kotlin 简洁性的特征之一。
- Kotlin中,给属性直接赋值,或者调用属性的值,其实质是调用了访问器 get、set 方法,这点跟java是不同的。
- 如果你需要改变一个访问器的可见性或者对其注解,但是不需要改变默认的实现, 你可以定义访问器而不定义其实现。
也就是在类外禁止改变 属性var 的值,可以将其声明为val 或者 自定义var set访问器的可见性 private 如下
class Kot{
var allByDefault:Int ?= null
get() = if(field ==null){
0
}else{
field
}
//用private进行修饰
private set(value){
field = if(value==null){
0
}else{
value-2
}
}
}
//Kot.allByDefault= 1//error: 不允许设置
幕后字段
- Kotlin 中不能直接声明字段,当属性需要字段时,会提供一个幕后字段。用field标识
- Kotlin 中存在幕后字段的情况:
- 属性访问器默认实现会隐式使用幕后字段。
- 自定义访问器通过field显式引用幕后字段。
var age : Int = 15
//上面这句代码其实和下面的代码是等价的,没有区别。
var age: Int = 15
set(value) {
field = value
}
get() {
return field
}
上面是kotlin对于幕后字段的使用,如何理解属性和字段呢? 比如在java中,一个person类中,在外部看来有一个getAge()和setAge(int age)方法,说明person有一个age 属性, 但是不能说person中一定有age这个字段。而在kotlin 中 可以通过关键字var/val 声明属性,但是没有关键字来声明字段:如下:
//java
class Person {
private int age =15; //age 是一个字段
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
new Person.getAge()//获取person的age属性
//kotlin
class Person {
var age: Int = 15//person 的一个age属性
set(value) {
field = value // field 为一个幕后字段的标识
}
get() {
return field
}
}
以上两段代码生成的.class文件是相同的。这就是我对 backing field 的理解。
幕后属性
kotlin 中可以通过声明一个私有的属性作为一个幕后属性
class Base{
private var _table: Map? =null
public val table: Map
get() {
if (_table == null) {
_table = HashMap() //类型参数已推断出
}
return _table ?: throw AssertionError("Set to null by another thread")
}
}
fun main(args: Array) {
var base = Base()
println("table = ${base.table}" )
}
幕后属性的使用与java的字段相似,解决隐式幕后字段不能处理的问题,通过私有属性的get/set来优化。也是Kotlin对于空指针的一种解决方案。
编译期常量
通过 const 修饰的属性 满足如下条件:
- 位于顶层或者是 object 的一个成员
- 用 String 或原生类型 值初始化
- 没有自定义 getter
//编译期常量
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { …… }
延迟初始化属性与变量
通过 lateinit 修饰的属性 满足如下条件:
- 只能用于在类体中的属性(不是在主构造函数中声明的 var 属性,并且仅当该属性没有自定义 getter 或 setter 时)
- 用于顶层属性与局部变量。该属性或变量必须为非空类型,并且不能是原生类型
- 在初始化前访问一个 lateinit 属性会抛出一个特定异常,明确标识该属性被访问及它没有初始化
//延迟性属性的使用如下:
public class MyTest {
class TestSubject{
fun method(){
println("TestSubject.method()")
}
}
lateinit var subject: TestSubject
fun setup() {
subject = TestSubject()
}
fun test() {
subject.method() // 直接解引用
}
}
fun main(args: Array) {
var test = MyTest()
test.setup()//如果不调用setup()初始化,会报如下错误
test.test()
}
初始化前使用延迟性属性,会报如下错误:
Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property subject has not been initialized
at MyTest.test(Simplest version.kt:12)
at Simplest_versionKt.main(Simplest version.kt:18)
检测一个 lateinit var 是否已初始化 可以通过引用属性的.isInitialized来做判断:
class Foo {
lateinit var bar: String
fun setup() {
println("sssss ${this::bar.isInitialized}" )
bar="value"
println("xxxxx ${this::bar.isInitialized}")
}
}
fun main(args: Array) {
Foo().setup()
}
输出结果:
sssss false
xxxxx true
属性和字段的基本使用就写完了,由于篇幅问题,再加上一个接口的学习。
接口
接口的定义与实现
Kotlin 中使用关键字 interface 来定义接口,包含抽象方法的声明、也可以有方法的实现。这就跟java有区别了,如果声明的方法没有方法体,默认是抽象方法,子类必须实现,有方法体的子类可以选择性实现。
interface MyInterface {
//默认抽象方法
fun bar()
fun foo() {
// 可选的方法体
}
}
class Child : MyInterface {
override fun bar() {
// 方法体
}
}
接口中定义的属性
Kotlin 中接口可以定义抽象属性 或者提供访问器的实现,但是访问器的实现不能包含幕后字段(上面有对幕后字段的理解)。
interface MyInterface {
val prop: Int // 抽象的
val propertyWithImplementation: String
get() = "foo"
fun foo() {
println(prop)
println(propertyWithImplementation)
}
}
class Child : MyInterface {
override val prop: Int = 29
override val propertyWithImplementation: String
get() = super.propertyWithImplementation
}
fun main(args: Array) {
println(Child().propertyWithImplementation)
Child().foo()
}
输出结果:
foo
29
foo
解决覆盖冲突
现多个接口时,可能会遇到同一方法继承多个实现的问题,在类的继承中已经讲解过,请参考上一篇。
参考
http://www.jianshu.com/writer#/notebooks/19396434/notes/22305825/preview
https://liyuanbiao.wordpress.com/2017/07/30/ru-he-li-jiekotlin-zhong-shu-xing/