Kotlin-Anko学习(5)Kotlin语法-属性、字段、接口

本系列文章来学习 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 中存在幕后字段的情况:
    1. 属性访问器默认实现会隐式使用幕后字段。
    2. 自定义访问器通过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/

你可能感兴趣的:(Kotlin-Anko学习(5)Kotlin语法-属性、字段、接口)