在前几篇文章中我们详细的介绍了Kotlin中的类与继承、接口、object关键字的使用。而Kotlin中的类是丰富多彩的,还有数据类、密封类、枚举类,它们在Kotlin中都扮演着十分重要的角色,这篇文章我们就来详细的介绍下有关这几个类的知识点。下面我们开始本篇文章的学习~
1.数据类
在Kotlin中如果我们想要声明一个仅仅用来保存数据的类,我们使用关键字data
,并将其放在class
关键字之前。如下代码示例,我们声明一个数据类Person:
kotlin
data class Person(
var name: String,
var age: Int
)
并在Person类的主构造函数中声明了两个属性name和age。关于声明一个数据类,我们有以下两点需要注意:
- 1.数据类的主构造函数中至少要有一个参数
- 2.并且构造函数中的参数需要声明为
var
或val
在类与继承的文章中我们已经介绍过在主构造函数中的参数加上var
或val
修饰,就是将该参数定义成了该类的属性,其实和我们在类体中声明的属性在使用上没有任何的区别,只是声明的位置在主构造函数而已。当然我们也可以在Person类的类体中去声明一个属性。
kotlin
data class Person(var name: String) {
var age: Int = 20
}
只是在数据类中,编译器自动生成的函数只会涉及主构造函数中的属性,不涉及类体中的属性。如上示例代码中的写法,Kotlin编译器为数据类Person生成的toString()、equals()、hashCode()、copy()函数,它们都只涉及到name属性,不会涉及到类体中的age属性。包括ComponentN函数亦是如此。
2.有趣的解构声明
我们每在数据类的主构造函数中声明一个属性,该类都会自动生成一个ComponentN函数与之顺序对应。例如我们在上面介绍的Person类,该类中的name属性就对应一个component1()函数,age属性对应一个component2()函数。在Android Studio中依次打开Tools -> Kotlin -> Show Kotlin Bytecode在右边的弹出框中我们点击Decompile按钮,得到如下反编译的Java代码:
从上图中标记的2处我们可以看到Person类中属性所对应的ComponentN函数。而CoponentN函数正和我们要介绍的解构声明有关。有时把一个对象解构成多个变量会很方便,例如,我们将Person对象解构成变量name和age:
kotlin
fun main() {
val (name, age) = Person("Jack", 20)
}
这种将一个对象拆解成多个变量的方式,我们称之为解构声明。而解构出来的变量,等价于被解构类调用其对应的ComponentN函数。按照上面同样的方式,我们将上面的示例代码反编译成Java代码,为了方便阅读对部分代码进行了一些调整:
ini
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进行遍历的时候我们就可以使用解构声明,如下代码示例:
kotlin
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表达式中使用解构声明:
kotlin
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
当函数类型有多个参数时,使用逗号将解构对和参数分开:
kotlin
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
最后再补充一个小知识点。自Kotlin1.1后,如果在解构声明中你不需要某个变量,我们也可以像Lambda表达式中的参数一样,使用下划线取代其名称:
scss
val (name, _) = Person("Json", 18)
而这种写法的好处就是,Kotlin编译器也不会再去调用相应的ComponentN函数。
3.copy函数
有时候我们希望在已有对象的基础上稍作修改后获取另一个新的对象,这时copy函数就会变得很有用:
ini
val person = Person("Json", 20)
val newPerson = person.copy(name = "Jack")
copy函数的实现类似如下代码示例:
kotlin
fun copy(name: String = this.name, age: Int = this.age) = Person(name, age)
4.Pair和Triple
Pair和Triple是Kotlin为我们提供的内置数据类。Pair类有两个属性参数,我们常常使用中缀函数to来创建一个Pair对象:
ini
val point = 10 to 100
我们也可以使用Pair的扩展函数toList将一个Pair对象转成一个List对象:
ini
val list = point.toList()。
而Triple则是声明了三个属性参数的数据类,它也为我们提供了toList的扩展函数:
scss
Triple(1, 2, 3).toList()
5.密封类
在Kotlin中声明一个密封类使用关键字sealed
,在类声明的时候将其放在关键字class
之前。例如我们将某车厂所生产的车型定义成一个密封类,并扩展以下子类:
kotlin
sealed class CarType
class Car : CarType()
class SUV : CarType()
class BUS : CarType()
密封类用来表示受限的类继承结构:当一个值为有限的几种类型、而不能有任何其他类型。在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。使用密封类的一个好处是,当我们使用when
作为表达式的时候,只要覆盖到该密封类的所有子类型,无需再编写else
块:
kotlin
fun main() {
println(getCarName(Car()))
}
fun getCarName(carType: CarType) : String {
return when(carType) {
is Car -> "Car"
is SUV -> "SUV"
is BUS -> "BUS"
}
}
// 输出
Car
密封类本身是抽象的,我们没有办法直接创建一个密封类的对象,在密封类的类体中可以为其定义抽象方法或者属性:
kotlin
sealed class CarType {
abstract val name: String
abstract fun info() : String
}
6.枚举类
和Java语言一样,在Kotlin中我们声明一个枚举类使用关键字enum
。如下代码示例,我们声明一个名为LoadState的枚举类:
kotlin
enum class LoadState {
Success,
Failure
}
每个枚举常量都只存在一个实例。它们都拥有一个name属性和一个ordinal属性。name用来表示该枚举常量的名称,ordinal用来表示枚举常量在声明时的顺序。我们可以使用枚举类的values()方法来获取其所有常量所在的数组。如果你想获取枚举类中所有常量的信息,就可以这么写:
kotlin
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异常,如下代码示例:
kotlin
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)
当我们传入正确的枚举常量名称时:
kotlin
fun main() {
println(LoadState.valueOf(LoadState.Success.name))
}
// 输出
Success
我们可以在主构造函数中给枚举类新增属性:
scss
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
和密封类一样,枚举类本身就是抽象的,我们也可以为其添加抽象属性和方法:
kotlin
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>()
函数以泛型的方式访问枚举类中的常量 :
kotlin
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中的基础知识-操作符重载,我们下期再见~