Kotlin

Kotlin语法糖--类和对象(中)

2017-08-22  本文已影响75人  皮球二二

在上篇的学习中,我们将Kotlin中类的相关概念进行了学习,这篇我们就对对象属性进行学习

Kotlin中的类可以有属性,属性用关键字var表明为变量,用val表明为常量。要使用一个属性,直接通过名称引用即可

fun main(args: Array<String>) {
    val a=A()
    a.valueA
    a.valueB
}

class A {
    val valueA = 1
    var valueB = 2
}

声明一个属性的完整语法为

var <propertyName>[: <PropertyType>] [=  <property_initializer>]
      [<getter>]
      [<setter>]

其初始化属性部分、getter、setter部分都是可选的。属性的类型如果可以从初始值或者getter返回值推断出来,这个也可以省略掉,但是getter推断仅限于常量属性。常量属性不会有setter方法
来看下getter、setter的完整写法

class B {
    var valueC: Int = 10
        get() {
            return field
        }
        set(value) {
            field=value+1
        }
}

简单说明一下
(1) 除了getter/settter结构外,我们会注意到field标识符,我们后面会具体说一下,这里我就简单的告诉你重新赋值跟重新取值需要它
(2) setter里面的参数value就是你赋值时传入到属性中的值,默认是value,你要是自己愿意也可以改成其他的
(3) 如果你需要改变一个访问器的可见性或者对其添加注解,但不需要改变默认的实现,你可以定义访问器而不定义其实现方式,好比这样

var valueB: Int = 12
    private set

因为valueB的setter方法是私有的,所以你无法对它进行赋值了
在Java中通过setter/getter方法来使用相应的属性,像这样

A a=new A();
a.getValueA();
a.setValueB(3);

Kotlin中有注解@JvmField,用来指示Kotlin编译器不为该属性生成getter/setter方法,并将其作为字段公开。

@JvmField var valueC = 3
System.out.println(a.valueC);

其实这个地方我是有点困惑的,之前看了很多篇文章对这个地方的解释,感觉都不很清楚,那我就按照自己的理解来说明吧。当你自定义访问器的时候,Kotlin会自动提供一个备用字段,使用刚才那个field标识符去访问备用变量。field标识符只能用在属性的访问器内。刚才初始化的时候那个valueB的值12就会被写入备用字段中,所以field的值就是这么来的

我们可以使用const修饰符来编辑编译期常量。编译期常量需要满足以下要求
(1) 位于顶层或者是object的一个成员
(2) 用属性或者原生类型值进行初始化
(3) 没有自定义getter
Java中的编译期常量为final。有人说我们不是之前有了val来表明常量了吗,这边还要一个干嘛?那我们就来看看作用吧

const val ANDROID: String = "1"

object C {
    const val XIAOMI: String = "2"
    val HUAWEI: String = "3"
}

class D {
    companion object {
        const val XIAOMI: String = "3"
        val HUAWEI: String = "4"
    }
}

看好这个const修饰符使用的位置,来看看引用的地方

ANDROID
C.XIAOMI
C.HUAWEI
D.XIAOMI
D.HUAWEI

在Kotlin中貌似没有什么不同,就这样了吗?来看看字节码部分


字节码

这个应该就显而易见了。以object C为例,不是编译期常量的话,在java中是不能直接通过名称访问的,只能通过gettter去访问;而编译期常量直接是一个公开的属性
之前提到的@JvmField,如果用它来修饰val变量,就和const关键字加不加功能都一样

这个在系列最开始的时候已经介绍过了。如果你使用依赖注入来进行初始化的话,我们的属性声明是没有必要一开始就初始化的,这个时候就需要用到惰性初始化了
在变量中我们使用lateinit这个修饰符标记该属性

class E {
    lateinit var value: String
}

使用时如果你不先初始化而直接使用,那么就妥妥的报错了

val e = E()
e.value="Hello LateInit"
println(e.value)

与Java不同的是,Kotlin需要显式的标注可覆盖的成员和覆盖后的成员,怎么显式标注?直接写open和override就行

open class SuperClassB {
    open fun getValue() : String {
        return "1"
    }
}
class F : SuperClassB() {
    override fun getValue() : String {
        return "1"
    }
}

你要是说我忘记加了怎么办?放心好了,编译器不会让你通过的
还有一种情况,就是我这个子类又被别人继承了,我不想让子类的方法被别人覆盖,怎么办?也很简单,直接加一个final即可

open class F : SuperClassB() {
    final override fun getValue() : String {
        return "1"
    }
}

class G : F()

同覆盖方法一样,属性也是可以被覆盖的,也是要有open跟override。每个声明的属性可以由具有初始化器的属性或者具有getter方法的属性覆盖

class I

open class SuperClassC {
    open var i:I? = null
    open var a: String = "123"
    open val b: String = "456"
    open val c: String = "789"
}

class H : SuperClassC() {
    override var a: String
        get() = super.a
        set(value) {}

    override val b: String
        get() = super.b

    override var c: String
        get() = super.c
        set(value) {}  // 不是必须

    override var i: I?
        get() = super.i
        set(value) {}  
}

你可以用一个var属性来覆盖一个val属性,但反之不可,因为属性本质是声明了一个getter方法,而将其覆盖为var只是相当于额外声明一个setter方法而已
你还可以将属性覆盖放到主构造函数上去,成为属性声明的一部分

class K(override var i: I?, override var a: String, override val b: String, override val c: String) : SuperClassC()

Kotlin中,如果一个类继承的方法或者属性有多个实现,那么它必须覆盖这个成员并提供其自己的实现,为了表示它采用从哪个超类或者接口的实现,我们使用super关键字并且在其后添加相应的超类或者接口的类型,如super<Base>

open class M {
    open fun getValue() : String {
        return "1"
    }
}

interface N {
    fun getValue() : String {
        return "2"
    }
}

class J : M(), N {
    override fun getValue(): String {
        super<M>.getValue()
        super<N>.getValue()
        return "J"
    }
}

同时继承M、N没有问题,问题在于getValue方法有2个实现,所以你必须制定super的类型来消除歧义,或者像文章这里2个getValue同时使用

这个篇幅比较大,我们放到后面详细阐述

Kotlin的接口与Java差不多,它既包含抽象方法的声明,也包含方法的实现。与抽象类不同的是,接口无法保存状态。接口可以有属性,但是必须声明为抽象的,或者提供访问器实现,并且不能有备用属性

interface F {
    fun getValue()
    fun getValue1() {
        println("getValue1")
    }
    val a: Int
    val b
    get() = "2"
    val c: Int
}

来看看接口的实现

class G : F {
    override val c: Int
        get() = 100

    override fun getValue() {
        getValue1()
    }

    override var a: Int = 0
        get() = field
    set(value) {
        field = b.toInt()*10
    }
}

这里同继承是一样的,接口也可以使用var属性覆盖val属性,但是要提供初始化值,接口内部实现好的方法也是可以直接使用的

这个在之前的覆盖规则中已经提及,此处不再赘述

类、对象、接口、构造函数、函数、属性以及他们的getter(通常与他的属性有相同的可见性)、setter都有可见性修饰符。Kotlin中的可见性修饰符有4种:public、internal、protected、private。如果没有显式的指定可见性修饰符的话,默认是public
public:任何地方都可以使用
private:只能在当前文件内使用
protected:在当前文件以及其子类中可见
internal:当前模块内的任何地方可以使用
还有几个地方要注意:
(1) 外部类不能直接访问内部类的private成员
(2) 如果你覆盖了一个protected成员并且没有显式指定其可见性,那么该成员依然还是protected

我们一般在Java代码中会写很多很多工具类,比如StringUtils、NumberFormatUtils这种。如果这些工具类被打成jar的话,我们可能还需要通过继承的方式来扩展相应工具类的功能。但是Kotlin中不需要这样麻烦,让我们来看看吧

class I {
    fun printlnI() {
        println("printlnI")
    }
}

本来这个I类只有一个printlnI函数,但是我想让他打印点别的东西,怎么做呢?

fun I.printlnI1() {
    println("printlnI1")
}

直接在顶层添加扩展,这样就给I新增了一个printlnI1函数

val i=I()
i.printlnI1()

需要注意的是,扩展并没有真正的修改它所扩展的类,而仅仅让这个类的实例对象通过.调用新的函数。扩展函数的调用是由发起函数调用的表达式的类型决定的,而不是运行时动态获得的表达式类型决定的。我们来验证一下这个结论

open class J
class K: J()
fun J.printlnValue() {
    println("J")
}
fun K.printlnValue() {
    println("K")
}
fun printValue(j: J) {
    j.printlnValue()
}

运行一下

printValue(K())

看看结果


结果

因为扩展函数的调用只取决于声明参数的类型,这里声明的类型为J
如果一个类的成员函数与扩展函数函数签名相同,那么只会调用成员函数

class I {
    fun printlnI() {
        println("printlnI")
    }
}

fun I.printlnI() {
    println("printlnI__")
}

运行一下

println(I().printlnI())

看看结果


运行结果

当然函数签名不同的话,还是会执行新定义的函数的

函数都可以扩展了那属性当然不甘示弱了

val <T> List<T>.lastValue : T
        get() = this[this.size-1]

这里是对List进行扩展,通过LastValue,返回当前集合的最后一个值。
需要注意的是,同扩展函数一样,扩展属性也不是真正的将这个属性添加到类中,因此也没办法让扩展属性拥有备用字段,它只能通过明确提供的getter或者setter方法才能定义

class D {
    companion object {
        const val XIAOMI: String = "3"
        val HUAWEI: String = "4"
    }
}

fun D.Companion.printlnValue() {
    println(D.XIAOMI)
}

val D.Companion.ZHONGXING
    get() = "5"

使用倒是没有差别

println(D.printlnValue())
println(D.ZHONGXING)

看看如下情况

class L {
    fun a() {
        println("L")
    }
}

class M {
    fun a() {
        println("M")
    }

    fun L.printlnValue() {
        a()
    }

    fun addL(l: L) {
        l.printlnValue()
    }
}

L、M中都有a函数,那么我在执行addL函数之后,会打印谁呢?

M().addL(L())

来看看结果


运行结果

我们一般称扩展声明所在类的实例为分发接收者,比如这里的M;另外一个扩展方法调用的所在接收者类型的实例成为扩展接收者,比如这里的L。当这种接收者成员名字冲突的时候,扩展接收者优先执行。如果你要引用分发接收者,那么就得使用this@label,比如这里应该是this@M.a()
扩展函数与属性也可以被声明为open在子类中被覆盖

open class N {
    open fun L.b() {
        println("N")
    }

    open val L.num
        get() = 100

    fun printlnValue(l: L) {
        l.b()
        println(l.num)
    }
}

class O : N() {
    override fun L.b() {
        println("O")
    }

    override val L.num: Int
        get() = 200
}

运行一下

O().printlnValue(L())

看看结果


运行结果

得到的是子类中运行的结果

上一篇 下一篇

猜你喜欢

热点阅读