一起来学Kotlin:概念:9. Kotlin Class:数据类(data class),枚举类(Enum Class),密封类(Sealed Class)
在这篇博客中,我们将详细介绍 Kotlin 各种类,包括类的构造函数,数据类(data class),枚举类(Enum Class),密封类(Sealed Class)。
文章目录
- 一起来学Kotlin:概念:9. Kotlin Class:数据类(data class),枚举类(Enum Class),密封类(Sealed Class)
- 1 最初级类介绍
- 2 构造函数(constructor)
- 2.1 主构造函数(primary constructor)
- 2.2 辅助构造函数(Secondary Constructor)
- 3 数据类(data class)
- 4 枚举类(Enum Class)
- 4.1 基本定义
- 4.2 枚举包含构造函数
- 4.3 枚举常量作为匿名类
- 4.3 Enum class 和 Interface 联用
- 5 密封类(Sealed Class)
1 最初级类介绍
类声明由类名、类头(指定其类型参数、主构造函数等)和类主体组成,用花括号括起来。 标题和正文都是可选的; 如果类没有主体,可以省略花括号。
class Customer
// 声明一个名为 Customer 的类,没有任何属性或用户定义的构造函数。 Kotlin 会自动创建一个非参数化的默认构造函数。
class Contact(val id: Int, var email: String)
// 声明一个具有两个属性的类:不可变参数 id 和可变参数 email ,以及一个具有两个参数 id 和 email 的构造函数。
fun main() {
val customer = Customer()
// 通过默认构造函数创建类 Customer 的实例。 请注意,Kotlin 中没有 new 关键字。
val contact = Contact(1, "mary@")
// 使用带有两个参数的构造函数创建 Contact 类的实例。
println(contact.id)
contact.email = "jane@"
}
2 构造函数(constructor)
可能我们在看上面介绍的时候会有一个问题,在我们在定义 Contact
这个类的时候,我们并没有初始化,但我们依然可以直接调用这个类定义的参数:id
以及email
。
2.1 主构造函数(primary constructor)
主构造函数是类头的一部分。 这是一个例子:
class Person(val firstName: String, var age: Int) {
// class body
}
括号内的代码块是主构造函数:(val firstName: String, var age: Int)
。
构造函数声明了两个属性:firstName
(使用关键字 val
声明的只读属性)和 age
(使用关键字 var
声明的读写属性)。
这就是为什么我们可以直接调用类头定义的参数的原因,比如:
fun main(args: Array<String>) {
val person1 = Person("Joe", 25)
println("First Name = ${person1.firstName}") // First Name = Joe
println("Age = ${person1.age}") // Age = 25
}
class Person(val firstName: String, var age: Int) {
}
创建 Person
类的对象时,传递了 Joe
和 25
这两个值,就好像 Person
是一个函数一样。这将 1person11 对象的 firstName
和 age
属性分别初始化为 Joe
和 25
。
当然,我们也可以在类中定义初始化模块,例子如下:
fun main(args: Array<String>) {
println("person1 is instantiated")
val person1 = Person("joe", 25)
println("person2 is instantiated")
val person2 = Person("Jack")
println("person3 is instantiated")
val person3 = Person()
}
class Person(_firstName: String = "UNKNOWN", _age: Int = 0) {
val firstName = _firstName.capitalize()
var age = _age
// initializer block
init {
println("First Name = $firstName")
println("Age = $age\n")
}
}
这里有两个点需要注意:如果我们在类中使用的变量并不是类头定义的,那么一般我们在类头的变量前面加一个_
;另外一个,类头定义的变量可以设置默认值。
2.2 辅助构造函数(Secondary Constructor)
在 Kotlin 中,一个类也可以包含一个或多个辅助构造函数。 它们是使用 constructor
关键字创建的。
辅助构造函数在 Kotlin 中并不常见。 当您需要扩展一个提供多个以不同方式初始化该类的构造函数的类时,会出现辅助构造函数最常见的用法。
以下是在 Kotlin 中创建辅助构造函数的方法:
class Log {
constructor(data: String) {
// some code
}
constructor(data: String, numberOfData: Int) {
// some code
}
}
在这里,Log
类有两个辅助构造函数,但没有主构造函数。
我们可以将类扩展为:
class Log {
constructor(data: String) {
// code
}
constructor(data: String, numberOfData: Int) {
// code
}
}
class AuthLog: Log {
constructor(data: String): super(data) {
// code
}
constructor(data: String, numberOfData: Int): super(data, numberOfData) {
// code
}
}
这里,派生类(derived class
)AuthLog
的构造函数调用基类 Log
对应的构造函数。 为此,使用了 super()
。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zQ2vJx5v-1666351455129)(./img/9_1.png)]
在 Kotlin 中,您还可以使用 this() 从同一类的另一个构造函数(如在 Java 中)调用构造函数。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jh84T91S-1666351455130)(./img/9_2.png)]
3 数据类(data class)
数据类使创建用于存储值的类变得容易。 此类类自动提供用于复制、获取字符串表示和在集合中使用实例的方法。 我们可以在类声明中使用自己的实现来覆盖这些方法。
在下面这段代码中,我们可以非常清晰地看到,Kotlin数据类的特点。
data class User(val name: String, val id: Int) { // Defines a data class with the data modifier.
override fun equals(other: Any?) =
other is User && other.id == this.id // Override the default equals method by declaring users equal if they have the same id.
}
fun main() {
val user = User("Alex", 1)
println(user)
// 打印结果:User(name=Alex, id=1)
// toString 方法是自动生成的,这使得 println 可以直接输出。
val secondUser = User("Alex", 1)
val thirdUser = User("Max", 2)
println("user == secondUser: ${user == secondUser}") // 打印结果:user == secondUser: true。需要注意的是,这里的 == 被 override过了,比对的是两个id是否一致。
println("user == thirdUser: ${user == thirdUser}") // 打印结果:user == thirdUser: false
// hashCode() function
println(user.hashCode()) // 打印结果:63347075。 具有完全匹配属性的数据类实例具有相同的 hashCode。
println(secondUser.hashCode()) // 打印结果:63347075
println(thirdUser.hashCode()) // 打印结果:2390846
// copy() function
println(user.copy()) // 自动生成的 copy 函数让我们更容易创建新实例。打印结果:User(name=Alex, id=1)
println(user === user.copy()) // 打印结果:false。由于 copy 创建的是一个新实例,因此对象及其副本具有不同的引用。
println(user.copy("Max")) // 打印结果:User(name=Max, id=1)。复制时,您可以更改某些属性的值。 copy 以与类构造函数相同的顺序接受参数
println(user.copy(id = 3)) // 打印结果:User(name=Alex, id=3)。使用带有命名参数的副本来更改值,而不管属性顺序如何。
println("name = ${user.component1()}") // 打印结果:name = Alex。自动生成的 componentN 函数让我们可以按照声明的顺序获取属性的值。
println("id = ${user.component2()}") // 打印结果:id = 1
}
从上面的代码中,我们可以看到,对于Kotlin数据类,编译器自动从主构造函数中声明的所有属性派生以下成员:
- equals()/hashCode()
- toString()
- componentN() 函数对应于其声明顺序中的属性
- copy() 函数
4 枚举类(Enum Class)
4.1 基本定义
枚举类用于对表示一组有限不同值的类型进行建模,例如方向、状态、模式等。
enum class State {
IDLE, RUNNING, FINISHED // 这里我们使用三个枚举常量定义一个简单的枚举类。枚举常量的数量总是有限的,而且它们都是不同的。
}
fun main() {
val state = State.RUNNING // 通过类名访问枚举常量。
val message = when (state) { // 使用枚举,这里可以使用 when,而不是 if else。
State.IDLE -> "It's idle"
State.RUNNING -> "It's running"
State.FINISHED -> "It's finished"
}
println(message) // 返回 It's running
}
所以,Enum Class的基本定义可以这么写:
enum class CardType {
SILVER, GOLD, PLATINUM
}
4.2 枚举包含构造函数
枚举可以像其他类一样包含属性和方法,用分号与枚举常量列表分隔。
enum class Color(val rgb: Int) { // 定义一个具有属性和方法的枚举类。
RED(0xFF0000), // 每个枚举常量都必须为构造函数参数传递一个参数。0xFF0000 就是 RED 中 rgb 的值。
GREEN(0x00FF00),
BLUE(0x0000FF),
YELLOW(0xFFFF00);
fun containsRed() = (this.rgb and 0xFF0000 != 0) // 枚举类成员与常量定义之间用分号隔开。这里的 返回我们到时候实例化的rgb的值,比如,,那么 就是 0xFF0000。然后,这个值和 0xFF0000 进行与操作,如果与下来 不是0,那么返回 true。
}
fun main() {
val red = Color.RED
println(red) // 打印结果:RED。默认的 toString 返回常量的名称,这里是“RED”。
println(red.containsRed()) // 打印结果:true。调用枚举常量的方法。
println(Color.BLUE.containsRed()) // 打印结果:false。通过枚举类名调用方法。
println(Color.YELLOW.containsRed()) // 打印结果:true。RED 和 YELLOW 的 RGB 值共享第一位 (FF),因此打印为“真”。
}
所以,由于枚举常量是枚举类的实例,因此可以通过将特定值传递给构造函数来初始化常量。除了上面这个例子,下面的代码也类似:
enum class CardType(val color: String) {
SILVER("gray"),
GOLD("yellow"),
PLATINUM("black")
}
val color =
就会返回 gray
值。
4.3 枚举常量作为匿名类
我们可以通过将它们创建为匿名类来定义特定的枚举常量行为。 然后常量需要覆盖 Enum 定义中定义的抽象函数。
enum class CardType {
SILVER {
override fun calculateCashbackPercent() = 0.25f
},
GOLD {
override fun calculateCashbackPercent() = 0.5f
},
PLATINUM {
override fun calculateCashbackPercent() = 0.75f
};
abstract fun calculateCashbackPercent(): Float
}
val cashbackPercent = ()
返回 0.25。
4.3 Enum class 和 Interface 联用
interface ICardLimit {
fun getCreditLimit(): Int
}
enum class CardType : ICardLimit {
SILVER {
override fun getCreditLimit() = 100000
},
GOLD {
override fun getCreditLimit() = 200000
},
PLATINUM {
override fun getCreditLimit() = 300000
}
}
val creditLimit = CardType.PLATINUM.getCreditLimit()
println(creditLimit) // 打印返回 300000
for (cardType in CardType.values()) {
println(cardType.getCreditLimit()) // 分别打印100000,200000,300000
}
5 密封类(Sealed Class)
密封类允许我们限制继承的使用。一旦我们声明了一个密封类,它就只能从声明密封类的同一个包中子类化。 我们不能在声明密封类的包之外进行子类化。
sealed class Mammal(val name: String) // 定义一个密封类
class Cat(val catName: String) : Mammal(catName) // 定义子类。需要注意的是,所有子类必须在同一个package中。
class Human(val humanName: String, val job: String) : Mammal(humanName)
fun greetMammal(mammal: Mammal): String {
when (mammal) {
is Human -> return "Hello ${mammal.name}; You're working as a ${mammal.job}" // 执行 smartcast,将 Mammal 投射到 Human。
is Cat -> return "Hello ${mammal.name}" // 执行 smartcast,将 Mammal 投射到 Cat。
} // else-case 在这里是不必要的。
}
fun main() {
println(greetMammal(Cat("Snowy"))) // 打印返回:Hello Snowy
}