Kotlin

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

2017-08-21  本文已影响205人  皮球二二

之前说过Kotlin中所有东西都是对象,那么这一节我们就来说一下类和对象

Kotlin使用关键字class来声明一个类

class A {
    
}

类由类名、类头(主构造函数等)和大括号包围的类体组成。其中类头和类体都是可选的,如果一个类没有类体,那么大括号可以省略,比如刚才的那个类可以这样

class A 

在Kotlin中,一个类可以有一个主构造函数和一个或多个次构造函数。主构造函数是类头的一部分,它跟在类名(或其他可选类型参数)后

class B constructor(value: String) {
    
}

如果这个主构造函数没有任何可见性修饰符,那么constructor关键字可以省略

class B(value: String) 

class C private constructor(val value: String)

主构造函数中不能包含任何代码,初始化的代码可以放到以init关键字作为前缀的初始化块中

class B(value: String) {
    var value_:String? = null
    init {
        value_=value
    }
}

除了init中初始化,我们还可以在类体中声明的属性上进行初始化使用

class B(value: String) {
    var value__:String = value
}

这样略显啰嗦,其实Kotlin还有更简洁的

class B2(val value: String) {
    
}

这样下来,就无需在init中将参数进行赋值操作,而是可以直接作为常亮或者变量使用了

在类体内,可以使用constructor来声明一个次构造函数

class B3 {
    var value_:String? = null
    constructor(value: String) {
        value_=value
    }
}

如果类由主构造函数,那么每个次构造函数需要直接委托或间接通过别的次构造函数委托给主构造函数。委托到主构造函数或者另一个次构造函数用this关键字即可

class B2(val value: String) {
    constructor(value: String, value1: String) : this(value) {
        println("constructor2")
    }
    constructor(value: String, value1: String, value2: String) : this(value, value1) {
        println("constructor3")
    }
    init {
        println("init")
    }
}

来看看执行流程

fun main(args: Array<String>) {
    var b2=B2("a", "b", "c")
    println(b2.value)
}
执行结果

因为二级构造函数从属于主构造函数,于是如果使用二级构造函数声明该类的实例,则系统会先调用主构造函数的init代码,再调用二级构造函数的自身代码。
如果一个非抽象类没有声明任何构造函数,那么编译器会为这个类生成一个不带参数的主构造函数,并且可见性是public。如果你不希望你的类可以对外访问,那么你就要加上private了
如果主构造函数所有的参数都有默认值,那么编译器会为这个类生成一个无参构造函数,他将使用默认值

其实刚才也不小心写出来了

var b2=B2("a", "b", "c")
var b3=B3()

注意Kotlin没有new关键字
嵌套类和内部类以及匿名内部类稍后再说

在Kotlin中所有类都有一个共同的超类叫Any,就跟Java中是Object一样,但是这2个没有任何关系
要显式的声明一个超类,我们可以将其放在类头的冒号之后

open class SuperClass

class D : SuperClass()

注意这里面的open关键字,没有添加open标记的类是不能被继承的。Kotlin实现了这个理念:要么为继承而设计,要么就禁止继承
如果超类有主构造函数,则子类必须要用超类的主构造函数就地初始化

open class SuperClassA(val value: String)
class D(value1: String, val value2: String) : SuperClassA(value1)

如果没有主构造函数,那么每个次构造函数都要使用super关键字或另一个次构造函数初始化其类型

open class SuperClassB

class E : SuperClass {
    constructor(value: String) : super(value)
    constructor(value: String, value1: String) : super(value, value1)
}
class E2 : SuperClass {
    constructor(value: String) : super(value)
    constructor(value: String, value1: String) : this(value)
}

仔细看看这3种情况的写法,细微之处有区别,我一开始一直写的有问题

抽象类基本上与Java差不多,就是不用加上open关键字了

abstract class O : M() {
    override abstract fun getValue(): String
}

abstract class P : N {
    override abstract fun getValue(): String
}

我们一般在开发过程中用Bean作为数据结构,但是Java中的Bean就是一个普通的类,而Kotlin中的数据类却不一样。

data class Q(val value: String)

编译器会自动从主构造函数中将声明的所有属性导出,并添加以下方法
(1) equqls()/hashcode()
(2) toString(),格式例如Q(value=Hello)
(3) componentN,这个N的顺序与属性的声明顺序一致
(4) copy()
(5) set()/get()
如果你在类体中显式的定义或者继承自其它基类型,则不会生成相应的函数
在使用过程中有有以下几点要注意
(1) 主构造函数必须至少有一个
(2) 主构造函数上所有参数需要标记为val或者var
(3) 数据类不能是抽象、开放、密封或者内部类

Kotlin提供了对象进行复制功能,并且可以修改其中部分属性,其他地方保持不变

val q = Q("Hello")
println(q.toString())
val r: Q = q.copy(value = "World")
println(r.toString())
执行结果

看下Component的使用

r.component1()

就这么简单
还有一种表述方式,使用上显得更加显著一些

val (value1, value2, value3) = R(value = "0", value1 = "1", value2 = "2")
println("$value1, $value2, $value3")

标准库提供了Pair和Triple,类似于swift中的元组概念,他们分别可以容纳2个元素和3个元素。你可以通过first这种序号或者componentN这种形式访问

val r2 = Triple("0", "1", "2")
println("${r2.first}, ${r2.second}, ${r2.third}")
println("${r2.component1()}, ${r2.component2()}, ${r2.component3()}")

密封类是Kotlin中的特有概念,用来表示受限的类继承结构,某种意义上他们又是枚举类的扩展。要声明一个密封类,在类名前加一个sealed修饰符即可。虽然密封类可以用子类,但是子类必须与密封类在相同的文件下声明
来看看类似枚举的功能

sealed class S {
    open fun printInfo(value: String) {
        println(value)
    }
    data class BeanA(val value: String) : S()
    data class BeanB(val value: String) : S()
}

fun choiceClass(s: S) {
    when(s) {
        is S.BeanA -> s.printInfo(s.value)
        is S.BeanB -> s.printInfo(s.value)
    }
}

好处在于使用when表达式的时候,会覆盖所有要被验证的情况,无需添加else了

这是Java里面也有的概念,因此也没啥重点说明的,看看代码上的区别即可

class T {
    private val VALUE="value"
    class T1 {
        fun printValue() {
            println("拿不到VALUE")
        }
    }
    inner class T2 {
        fun printValue() {
            this@T.VALUE
            println(VALUE)
        }
    }
}

来看看区别
(1) 内部类用inner标记,嵌套类不需要先初始化外部对象
(2) 内部类可以访问外部类的成员,例如成员属性,并且带有一个对外部类对象的引用。引用外部类this对象的方式 : var v=this@外部类名。嵌套类则不能访问外部类成员

采用对象表达式创建匿名内部类实例

class U {
    private var impl: Impl1? = null
    fun addImpl1(impl: Impl1) {
        this.impl=impl
    }
}

interface Impl1 {
    fun printValue1()
    fun printValue2()
}

看看使用的代码

val u=U()
u.addImpl1(impl = object : Impl1 {
    override fun printValue1() {

    }

    override fun printValue2() {

    }
})

匿名内部类在实际场景使用中作用相当大,试想一下android上的setOnClickListener这种。Kotlin中一般用对象表达式和对象声明来对这个概念稍微概括了一下

刚才的范例就是演示了对象表达式的使用方式,如果你有多个超类型,那么可以跟在冒号后面用逗号分隔指定。
有时候我们仅仅想用一个对象来包裹变量和局部方法,那么我们可以这样

fun z() {
    val obj = object  {
        val a="11"
        val b="22"
        fun printlnValue() {
            println("hello")
        }
    }

    obj.printlnValue()
    println("${obj.a} + ${obj.b}")
}

在Java中,如果要让匿名内部类访问其作用域的变量,我们要使用final关键字来修饰。Kotlin则不需要这么添油加醋

class Z {
    val a="1"
    
    fun b()=object {
        val c=a
    }
}

在类中,只可以通过将其设置为私有函数才能对匿名对象的成员进行访问

class Z {
    private fun b()=object {
        val c="123"
    }

    val d=b().c
}

这个其实是单例模式的声明

object a1 {
    fun printlnValue() {
        println("123")
    }
}

在object关键字的右边跟上一个名称,就像声明变量一样。对象声明不是声明一个表达式,所以不能用在赋值语句的右边
使用时直接使用.访问就行

a1.printlnValue()

也可以有超类型

object a1 : SuperClass("123"), N {
    fun printlnValue() {
        println("123")
    }

    override fun getValue(): String {
        return super.getValue()
    }
}

对象声明不能再局部作用域(即函数内部直接嵌套),但是他们可以嵌套到其他对象声明或非内部类中使用

由于Kotlin中不存在静态方法,所以如果你不想通过一个类的实例来调用类中的函数(比如工厂方法),那么你可以把它写成伴生对象。伴生对象使用上像Java中的静态方法调用,但是这完全不是一回事。来看看代码

class C private constructor() {
    companion object Factory{
        val instance: C=C()

        fun getCompanionValue() {
            println("getCompanionValue")
        }
    }

    fun getValue() {
        println("getValue")
    }
}

使用是这样的

println(C.instance.getValue())
println(C.getCompanionValue())

简单理解一下伴生对象,这个对象是Companion类的对象,伴随着C类而生.
Java中如果想使用伴生对象的话,得加上@JvmStatic注解,变成这样

companion object Factory{
    @JvmStatic val instance: C=C()

    @JvmStatic fun getCompanionValue() {
        println("getCompanionValue")
    }
}

这样在Java中就可以像静态方法一样使用了

C.getInstance();
C.getCompanionValue();

枚举使用起来跟Java也差不多,先看一个简单枚举类

enum class V {
    Left, top, right, bottom
}

再来看看带属性的枚举类

enum class W(val intValue:Int) {
    left(1), top(2), right(3), bottom(4);

    fun getDirection() = intValue
}

值得注意的是,如果要在枚举类中定义任何方法,就要使用分号把枚举常量列表和方法分开。
如果想直接访问枚举对象的属性,可以这样

W.left.getDirection()

遍历枚举键值得这样

for (value in W.values()) {
    println("${value.name}--${value.ordinal}")
}

枚举常亮也可以声明自己的匿名类

enum class X {
    WRITTING {
        override fun printValue() {
            println("WRITTING")
        }
    },
    READING {
        override fun printValue() {
            println("READING")
        }
    };

    abstract fun printValue()
}

通过定义抽象方法,可以完成匿名类的使用

可以通过枚举常量的名称获取相应的枚举常量

W.valueOf("left")

如果名称不匹配则会抛出异常

上一篇 下一篇

猜你喜欢

热点阅读