Kotlin语法基础篇九:丰富多彩的class

前言

在前几篇文章中我们详细的介绍了Kotlin中的类与继承接口object关键字。而Kotlin中的类是丰富多彩的,还有数据类、密封类、枚举类,它们在Kotlin中都扮演着十分重要的角色,这篇文章我们就来详细的介绍下有关这几个类的知识点。下面我们开始本篇文章的学习~

1.数据类

在Kotlin中如果我们想要声明一个仅仅用来保存数据的类,我们使用关键字data,并将其放在class关键字之前。如下代码示例,我们声明一个数据类Person:

data class Person(
    var name: String,
    var age: Int
)

并在Person类的主构造函数中声明了两个属性name和age。关于声明一个数据类,我们有以下两点需要注意:

  • 1.数据类的主构造函数中至少要有一个参数
  • 2.数据类主构造函数中的参数需要声明为`var`或`val`

类与继承的文章中我们已经介绍过在主构造函数中的参数加上var或val修饰,就是将该参数定义成了该类的属性,其实和我们在类体中声明的属性在使用上没有任何的区别,只是声明的位置在主构造函数而已。当然我们也可以在Person类的类体中去声明一个属性。

data class Person(var name: String) {
     var age: Int = 20
}

只是在数据类中,编译器自动生成的函数只会涉及主构造函数中的属性,不涉及类体中的属性。如上示例代码中的写法,Kotlin编译器为数据类Person生成的toString()、equals()、hashCode()、copy()函数,它们都只涉及到name属性,不会涉及到类体中的age属性。包括ComponentN函数亦是如此。

2.有趣的解构声明

我们每在数据类的主构造函数中声明一个属性,Kotlin编译器都会帮我们自动生成一个ComponentN函数与之顺序对应。例如我们在上面介绍的Person类,该类中的name属性就对应一个component1()函数,age属性对应一个component2()函数。在Android Studio中依次打开Tools -> Kotlin -> Show Kotlin Bytecode在右边的弹出框中我们点击Decompile按钮,得到如下反编译的Java代码:

从上图中标记的2处我们可以看到Person类中属性所对应的ComponentN函数。而CoponentN函数正和我们要介绍的解构声明有关。有时把一个对象解构成多个变量会很方便,例如,我们将Person对象解构成变量name和age: 

fun main() {
    val (name, age) = Person("Jack", 20)
}

这种将一个对象拆解成多个变量的方式,我们称之为解构声明。而解构出来的变量,等价于被解构类调用其对应的ComponentN函数。按照上面同样的方式,我们将上面的示例代码反编译成Java代码,为了方便阅读对部分代码进行了一些调整:

public static final void main() {
      Person person = new Person("Jack", 20);
      String name = person.component1();
      int age = person.component2();
}

由反编译出来的代码我们可以看到,变量name = preson.component1(),变量age = person.component2()。解构声明在实际开发中还是很有作用的,比如我们在对一个map进行遍历的时候我们就可以使用解构声明,如下代码示例:

fun main() {
    val map = mapOf(0 to "A", 1 to "B")
    for((key, value) in map) {
        println("key = $key, value = $value")
    }
}

// 输出
key = 0, value = A
key = 1, value = B

我们使用解构声明将类型为Map.Entry<Int,String>的对象解构成key、value变量。我们也可以在Lambda表达式中使用解构声明:

fun main() {
    normal { (name,age) -> println("name = $name, age = $age") }
}

private fun normal(block: (Person) -> Unit) {
    block.invoke(Person("Json", 18))
}

// 输出
name = Json, age = 18

当函数类型有多个参数时,使用逗号将解构对和参数分开:

fun main() {
    normal { (name, age), from -> println("name = $name, age = $age, from = $from") }
}

private fun normal(block: (Person, String) -> Unit) {
    block.invoke(Person("Json", 18), "normal")
}

// 输出
name = Json, age = 18, from = normal

List集合也可以使用解构声明,默认情况下最多支持获取其中的前五个元素。如下代码示例:

val (a, b, c, d, e) = listOf("A", "B", "C", "D", "E", "F")

最后再补充一个小知识点。自Kotlin1.1后,如果在解构声明中你不需要某个变量,我们也可以像Lambda表达式中的参数一样,使用下划线取代其名称:

val (name, _) = Person("Json", 18)

而这种写法的好处就是,Kotlin编译器也不会再去调用相应的ComponentN函数。

3.copy函数

有时候我们希望在已有对象的基础上稍作修改后获取另一个新的对象,这时copy函数就会变得很有用:

val person = Person("Json", 20)
val newPerson = person.copy(name = "Jack")

copy函数的实现类似如下代码示例:

fun copy(name: String = this.name, age: Int = this.age) = Person(name, age)

4.Pair和Triple

Pair和Triple是Kotlin为我们提供的内置数据类。Pair类有两个属性参数,我们常常使用中缀函数to来创建一个Pair对象:

val point = 10 to 100

我们也可以使用Pair的扩展函数toList将一个Pair对象转成一个List对象:

val list = point.toList()。

而Triple则是声明了三个属性参数的数据类,它也为我们提供了toList的扩展函数:

Triple(1, 2, 3).toList()

最后贴出Tuples.kt文件中的源码:

public data class Pair<out A, out B>(
    public val first: A,
    public val second: B
) : Serializable {
    public override fun toString(): String = "($first, $second)"
}

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

public fun <T> Pair<T, T>.toList(): List<T> = listOf(first, second)

public data class Triple<out A, out B, out C>(
    public val first: A,
    public val second: B,
    public val third: C
) : Serializable {
    public override fun toString(): String = "($first, $second, $third)"
}

public fun <T> Triple<T, T, T>.toList(): List<T> = listOf(first, second, third)

5.密封类

在Kotlin中声明一个密封类使用关键字sealed,在类声明的时候将其放在关键字class之前。例如我们将某车厂所生产的车型定义成一个密封类,并扩展以下子类:

sealed class CarType  
class Car : CarType()
class SUV : CarType()
class BUS : CarType()

密封类用来表示受限的类继承结构:当一个值为有限的几种类型、而不能有任何其他类型。在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。使用密封类的一个好处是,当我们使用when作为表达式的时候,只要覆盖到该密封类的所有子类型,无需再编写else块:

fun main() {
    println(getCarName(Car()))
}

fun getCarName(carType: CarType) : String {
    return when(carType) {
        is Car -> "Car"
        is SUV -> "SUV"
        is BUS -> "BUS"
    }
}

// 输出
Car

密封类本身是抽象的,我们没有办法直接创建一个密封类的对象,在密封类的类体中可以为其定义抽象方法或者属性:

sealed class CarType {
    abstract val name: String
    abstract fun info() : String
}

6.枚举类

和Java语言一样,在Kotlin中我们声明一个枚举类使用关键字`enum`。如下代码示例,我们声明一个名为LoadState的枚举类:

enum class LoadState {
    Success,
    Failure
}

每个枚举常量都只存在一个实例。它们都拥有一个name属性和一个ordinal属性。name用来表示该枚举常量的名称,ordinal用来表示枚举常量在声明时的顺序。我们可以使用枚举类的values()方法来获取其所有常量所在的数组。如果你想获取枚举类中所有常量的信息,就可以这么写:

fun main() {
    for(state in LoadState.values()) {
        println("name = ${state.name}, index = ${state.ordinal}")
    }
}

//输出
name = Success, index = 0
name = Failure, index = 1

valuesof(name:String)方法则是通过指定名称来获取具体的枚举常量,若通过名称未获取到该指定名称的枚举常量该方法将会抛出IllegalArgumentException异常,如下代码示例:

fun main() {
    println(LoadState.valueOf("Processing"))
}

// 输出
Exception in thread "main" java.lang.IllegalArgumentException: No enum constant com.study.myapplication.bean.LoadState.Processing
    at java.base/java.lang.Enum.valueOf(Enum.java:240)
    at com.study.myapplication.bean.LoadState.valueOf(LoadState.kt)
    at com.study.myapplication.bean.LoadStateKt.main(LoadState.kt:14)
    at com.study.myapplication.bean.LoadStateKt.main(LoadState.kt)

当我们传入正确的枚举常量名称时:

fun main() {
    println(LoadState.valueOf(LoadState.Success.name))
}

// 输出
Success

我们可以在主构造函数中给枚举类新增属性:

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF);
}

fun main() {
    for(color in Color.values()) {
        println(color.rgb)
    }
}

// 输出
16711680
65280
255

和密封类一样,枚举类本身就是抽象的,我们也可以为其添加抽象属性和方法:

enum class LoadState {
    Success {
        override val state = true
            },
    Failure {
        override val state = false
    };
    abstract val state:Boolean
}


fun main() {
    for(state in LoadState.values()) {
        println(state.state)
    }
}

// 输出
true
false

这里需要注意的一个点就是当我们给一个枚举类添加抽象属性或者方法的时候,需要在声明的最后一个枚举常量值后使用分号  ; 将其隔开。自Kotlin1.1起,可以使用 enumValues<T>()与enumValueOf<T>()函数以泛型的方式访问枚举类中的常量 :

fun main() {
    printAll<LoadState>()
    println(LoadState.Success)
}

inline fun <reified T:Enum<T>> printAll() {
    enumValues<T>().onEach { println(it.name) }
}

inline fun <reified R:Enum<R>> R.getEnum() : Enum<R> {
    return enumValueOf<R>(this.name)
}

// 输出
Success
Failure
Success

总结

有关Kotlin中类的相关知识,涉及到的细节知识点还是很多的。在类与继承的文章中我们已经介绍过了嵌套类和内部类的知识,这篇文章我们又介绍了数据类密封类、和枚举类。熟练的掌握类的相关基础知识,对于Kotlin开发来说是很有必要的,相信现在的你已经熟练的掌握了它,并可以在实际开发中灵活的运用它。好了,到这里我们这篇文章就介绍完了,下篇文章我们继续讲解Kotlin中的基础知识-操作符重载,我们下期再见~


 

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值