前言
本文主要讲解kotlin枚举类和扩展
Kotlin文章列表
Kotlin文章列表: 点击此处跳转查看
目录
1.1 枚举类
1.1.1 枚举类的基本用法
Kotlin中的枚举类(enum class)用于定义一组具有预定义值的常量。它们在许多情况下都很有用,例如表示一组相关的选项、状态或命名常量集合。下面是Kotlin枚举类的基本用法:
-
声明枚举类:
enum class Direction { NORTH, SOUTH, EAST, WEST }
-
使用枚举值:
val direction = Direction.NORTH println(direction) // 输出: NORTH
-
比较枚举值:
val direction = Direction.NORTH if (direction == Direction.NORTH) { println("向北") }
-
遍历枚举值:
for (enumValue in Direction.values()) { println(enumValue) }
-
获取枚举值的名称:
val direction = Direction.NORTH println(direction.name) // 输出: NORTH
-
获取枚举值的序号(从0开始):
val direction = Direction.NORTH println(direction.ordinal) // 输出: 0
-
使用枚举类的方法和属性:
enum class Direction { NORTH { override fun printDescription() { println("这是北方") } }, SOUTH { override fun printDescription() { println("这是南方") } }, EAST { override fun printDescription() { println("这是东方") } }, WEST { override fun printDescription() { println("这是西方") } }; abstract fun printDescription() } val direction = Direction.NORTH direction.printDescription() // 输出: 这是北方
这些是Kotlin枚举类的基本用法。
1.1.2 为枚举值指定对应的数值
在Kotlin中,你可以为枚举值指定对应的数值。默认情况下,每个枚举值会自动分配一个从0开始递增的整数值作为其数值。然而,你也可以显式地为枚举值指定自定义的数值。下面是为枚举值指定数值的几种方式:
-
基本数值分配:
enum class Direction { NORTH, SOUTH, EAST, WEST }
在这种情况下,
NORTH
的数值为0,SOUTH
的数值为1,以此类推。 -
自定义数值分配:
enum class Direction(val degrees: Int) { NORTH(0), SOUTH(180), EAST(90), WEST(270) }
在这个例子中,每个枚举值都有一个
degrees
属性来存储对应的数值。NORTH
的数值为0,SOUTH
的数值为180,以此类推。 -
使用匿名类和属性来分配数值:
enum class Direction { NORTH { override val degrees: Int = 0 }, SOUTH { override val degrees: Int = 180 }, EAST { override val degrees: Int = 90 }, WEST { override val degrees: Int = 270 }; abstract val degrees: Int }
在这个例子中,每个枚举值都通过匿名类来定义,并重写了
degrees
属性以指定数值。
你可以根据需要选择适合的方式来为枚举值指定数值。这样,你就可以在需要使用枚举值数值的地方访问这些数值。例如,对于第二个示例中的Direction
枚举类,你可以通过direction.degrees
来访问特定枚举值的数值。
1.1.3 枚举类的其他功能
除了基本用法外,Kotlin枚举类还提供了其他一些功能,使其更加灵活和强大。以下是一些Kotlin枚举类的其他功能:
-
声明方法:
枚举类可以声明方法,可以在每个枚举值上调用这些方法。例如:enum class Direction { NORTH { override fun getOpposite(): Direction = SOUTH }, SOUTH { override fun getOpposite(): Direction = NORTH }, EAST { override fun getOpposite(): Direction = WEST }, WEST { override fun getOpposite(): Direction = EAST }; abstract fun getOpposite(): Direction }
每个枚举值都实现了
getOpposite()
方法,根据当前方向返回相反的方向。 -
实现接口:
枚举类可以实现接口。这使得你可以为枚举类的每个枚举值提供不同的实现。例如:interface Printable { fun print() } enum class Direction : Printable { NORTH { override fun print() { println("北方") } }, SOUTH { override fun print() { println("南方") } }, EAST { override fun print() { println("东方") } }, WEST { override fun print() { println("西方") } }; }
在这个例子中,
Direction
枚举类实现了Printable
接口,并为每个枚举值提供了不同的print()
方法实现。 -
通过字符串获取枚举值:
你可以通过枚举值的字符串名称来获取对应的枚举值。使用valueOf()
函数实现这一点。例如:val direction = Direction.valueOf("NORTH") println(direction) // 输出: NORTH
这将返回枚举值
NORTH
。 -
定义扩展函数和属性:
你可以为枚举类定义扩展函数和属性,为枚举值添加额外的功能。例如:enum class Direction { NORTH, SOUTH, EAST, WEST } fun Direction.isVertical(): Boolean { return this == Direction.NORTH || this == Direction.SOUTH } val Direction.opposite: Direction get() = when (this) { Direction.NORTH -> Direction.SOUTH Direction.SOUTH -> Direction.NORTH Direction.EAST -> Direction.WEST Direction.WEST -> Direction.EAST }
在这个例子中,
isVertical()
是一个扩展函数,用于判断枚举值是否为垂直方向。opposite
是一个扩展属性,根据当前方向返回相反的方向。
这些是Kotlin枚举类的一些其他功能,它们使得枚举类更加灵活和可扩展。
1.2 扩展
1.2.1 扩展原生API
Kotlin提供了扩展函数和扩展属性的功能,使你能够扩展原生的API类,包括标准库和其他类库中的类。这使你能够在不修改原始类代码的情况下为这些类添加新的功能。下面是如何在Kotlin中扩展原生API的几种方法:
-
扩展函数:
你可以为任何类定义扩展函数。要定义一个扩展函数,你需要在函数名前面加上接收者类型并使用点符号。例如,以下是为字符串类型扩展一个reverse()
函数:fun String.reverse(): String { return this.reversed() }
使用示例:
val reversedString = "Hello, World!".reverse() println(reversedString) // 输出: "!dlroW ,olleH"
在这个例子中,我们扩展了
String
类,添加了一个reverse()
函数,以反转字符串的顺序。 -
扩展属性:
与扩展函数类似,你还可以为类定义扩展属性。要定义一个扩展属性,你可以使用get()
和(可选)set()
函数。以下是为Int
类型扩展一个isEven
属性:val Int.isEven: Boolean get() = this % 2 == 0
使用示例:
val number = 6 println(number.isEven) // 输出: true
在这个例子中,我们扩展了
Int
类,添加了一个isEven
属性,用于判断一个整数是否为偶数。 -
扩展标准库:
Kotlin的标准库提供了许多常见类型的扩展函数和属性,使你能够以更简洁的方式处理数据。例如,标准库中的List
类提供了许多方便的扩展函数,如filter()
、map()
和reduce()
等,用于对列表进行操作和转换。val numbers = listOf(1, 2, 3, 4, 5) val evenNumbers = numbers.filter { it % 2 == 0 } val doubledNumbers = numbers.map { it * 2 } val sum = numbers.reduce { acc, value -> acc + value }
在这个例子中,我们使用了
filter()
函数筛选出偶数,使用map()
函数将每个数字乘以2,使用reduce()
函数计算数字的总和。
扩展原生API使你能够根据自己的需求为现有类添加新的功能,提高代码的可读性和可维护性。请注意,扩展函数和属性仅在调用它们的作用域内可见,并且它们不能访问类的私有或受保护成员。
1.2.2 扩展自定义类
在Kotlin中,你不仅可以扩展原生的API类,还可以扩展自定义的类。这使你能够为自己的类添加新的功能或方法,以满足特定的需求。下面是如何在Kotlin中扩展自定义类的几种方法:
-
扩展函数:
你可以为自定义的类定义扩展函数,以添加新的功能。要定义一个扩展函数,你需要在函数名前面加上接收者类型并使用点符号。例如,假设你有一个名为Person
的自定义类,你可以为它添加一个greet()
函数:class Person(val name: String) fun Person.greet() { println("Hello, $name!") }
使用示例:
val person = Person("John") person.greet() // 输出: "Hello, John!"
在这个例子中,我们为
Person
类添加了一个greet()
函数,以便在实例上调用该函数时打印出问候语。 -
扩展属性:
与扩展函数类似,你还可以为自定义的类定义扩展属性。要定义一个扩展属性,你可以使用get()
和(可选)set()
函数。以下是为Person
类扩展一个age
属性:class Person(val name: String) val Person.age: Int get() = 30
使用示例:
val person = Person("John") println(person.age) // 输出: 30
在这个例子中,我们为
Person
类添加了一个age
属性,始终返回30。 -
扩展其他自定义类:
你可以为任何自定义类定义扩展函数和属性,无论它们是你自己编写的类还是来自其他类库。只需按照相同的语法和规则创建扩展函数和属性即可。例如:class Rectangle(val width: Int, val height: Int) fun Rectangle.isSquare(): Boolean { return width == height }
使用示例:
val rectangle = Rectangle(5, 5) println(rectangle.isSquare()) // 输出: true
在这个例子中,我们为
Rectangle
类添加了一个isSquare()
函数,用于判断矩形是否为正方形。
扩展自定义类使你能够在不修改原始类代码的情况下为它们添加新的功能。这样可以提高代码的可读性、可维护性和灵活性。扩展函数和属性仅在调用它们的作用域内可见,并且它们不能访问类的私有或受保护成员。
1.2.3 成员函数冲突的解决方案
在Kotlin中,当你扩展的类和目标类具有相同名称的成员函数时,会发生函数冲突。这种情况下,编译器无法确定应该使用哪个函数。为了解决这个问题,你可以采取以下两种解决方案:
-
显式指定调用的函数:
当发生函数冲突时,你可以使用类名作为限定符,显式指定调用哪个类的函数。例如,假设你有一个名为String
的扩展函数toUpperCase()
,但是String
类本身也有一个toUpperCase()
函数,你可以使用类名来调用扩展函数:fun String.toUpperCase(): String { return "Extension function" } fun main() { val str = "Hello" println(str.toUpperCase()) // 调用目标类的函数 println(str.String.toUpperCase()) // 调用扩展函数 }
在这个例子中,我们通过使用类名
String
作为限定符,显式调用了扩展函数toUpperCase()
和目标类的函数toUpperCase()
。 -
使用
@JvmName
注解:
另一种解决函数冲突的方法是使用@JvmName
注解为扩展函数指定一个不同的Java虚拟机名称。这样可以避免函数名称冲突。例如:@file:JvmName("StringUtil") package com.example.utils fun String.toUpperCase(): String { return "Extension function" }
在这个例子中,我们使用
@JvmName
注解将扩展函数toUpperCase()
的Java虚拟机名称设置为StringUtil.toUpperCase()
,避免了与目标类的函数冲突。注意:
@JvmName
注解应该放在扩展函数所在的文件的顶部。
通过显式指定调用的函数或使用@JvmName
注解,你可以解决函数冲突问题,并确保在扩展类和目标类具有相同名称的成员函数时,能够正确地调用所需的函数。
1.2.4 扩展属性
在Kotlin中,你可以通过扩展属性为类添加新的属性。扩展属性允许你为现有类添加新的属性,而无需修改其源代码。以下是如何在Kotlin中扩展属性的示例:
// 假设有一个名为Person的类
class Person(val name: String)
// 为Person类添加一个扩展属性age
val Person.age: Int
get() = 30
fun main() {
val person = Person("John")
println(person.age) // 输出: 30
}
在上述示例中,我们为Person
类添加了一个扩展属性age
。扩展属性的定义类似于扩展函数,但是在这种情况下,我们只需要定义get()
函数来获取属性的值。
请注意以下事项:
- 扩展属性不能有初始值,因为它们没有与之关联的字段。因此,你需要提供一个
get()
函数来计算属性的值。 - 扩展属性不支持
set()
函数,因此它们是只读的。
与扩展函数一样,扩展属性仅在调用它们的作用域内可见,并且它们不能访问类的私有或受保护成员。
通过使用扩展属性,你可以方便地为现有类添加新的属性,从而提供更灵活的数据操作和访问方式。
1.2.5 扩展伴随对象(CompanionObject)
在Kotlin中,你可以使用扩展函数和扩展属性来扩展伴随对象(companion object)。伴随对象是与类关联的单例对象,它可以包含类级别的函数和属性。以下是如何扩展伴随对象的示例:
class MyClass {
companion object {
fun greet() {
println("Hello from companion object!")
}
}
}
// 扩展伴随对象的函数
fun MyClass.Companion.sayHello() {
println("Hello from extension function of companion object!")
}
fun main() {
MyClass.greet() // 调用伴随对象的函数
MyClass.sayHello() // 调用扩展函数
}
在上述示例中,我们有一个包含伴随对象的MyClass
类。我们可以通过直接使用类名调用伴随对象的函数greet()
。
然后,我们使用扩展函数的语法来为伴随对象添加新的函数sayHello()
。这样,我们可以使用伴随对象的函数和扩展函数来进行调用。
请注意以下事项:
- 扩展伴随对象的函数或属性的定义需要使用
Companion
关键字作为限定符。 - 扩展伴随对象的函数和属性可以与伴随对象内部定义的函数和属性共存,它们在调用时使用相同的语法。
通过扩展伴随对象,你可以为伴随对象添加新的功能,而无需修改原始类的源代码。这使得你可以更灵活地扩展伴随对象并为其添加额外的行为。
1.2.6 扩展的范围
在Kotlin中,扩展的范围(可见性)是受限的,扩展函数和属性只在特定的作用域内可见。以下是关于Kotlin扩展范围的几点要注意的事项:
- 扩展函数和属性的作用域:
- 在顶层:你可以在文件的顶层定义扩展函数和属性,它们将在整个文件中可见。
- 在类内部:你可以在类内部定义扩展函数和属性,它们将在类的范围内可见。
- 可见性限制:
- 扩展函数和属性不能访问类的私有或受保护成员。它们只能访问类中的公共成员。
- 扩展函数和属性不会继承给子类。只有在扩展函数或属性定义所在的作用域内才可见,无法通过继承传递给子类。
- 导入扩展:
- 要使用扩展函数和属性,你需要在使用它们的文件中导入它们所在的包或文件。
- 如果扩展函数或属性在同一个包内,你不需要导入,可以直接使用。
- 如果扩展函数或属性在不同的包内,你需要使用
import
关键字导入它们。
例如,假设有以下代码:
package com.example.utils
fun String.reverse(): String {
return this.reversed()
}
class MyClass {
fun doSomething() {
val str = "Hello"
println(str.reverse())
}
}
fun main() {
val str = "World"
println(str.reverse())
}
在这个例子中,reverse()
扩展函数位于com.example.utils
包中。在main()
函数中,我们需要导入reverse()
函数所在的包才能使用它。而在MyClass
类的doSomething()
函数中,由于在同一个包内,所以无需导入即可使用。
总结起来,扩展函数和属性的范围是局限于定义它们的作用域内。你需要正确导入扩展函数和属性所在的包或文件,才能在其他地方使用它们。另外,扩展函数和属性无法访问类的私有或受保护成员。
1.2.7 在类中使用扩展
在Kotlin中,你可以在类内部使用扩展函数和扩展属性。这样做可以为类添加额外的功能,而无需修改类的源代码。以下是如何在类中使用扩展的示例:
class MyClass {
private val data: MutableList<String> = mutableListOf()
fun addData(item: String) {
data.add(item)
}
fun displayData() {
println("Data: $data")
}
// 在类内部定义扩展函数
fun MyClass.processData() {
data.forEach {
println("Processing: $it")
}
}
// 在类内部定义扩展属性
val MyClass.totalDataSize: Int
get() = data.size
}
fun main() {
val myObj = MyClass()
myObj.addData("Item 1")
myObj.addData("Item 2")
myObj.addData("Item 3")
myObj.displayData() // 输出: Data: [Item 1, Item 2, Item 3]
myObj.processData() // 输出: Processing: Item 1, Processing: Item 2, Processing: Item 3
println(myObj.totalDataSize) // 输出: 3
}
在上述示例中,我们在MyClass
类内部定义了一个扩展函数processData()
和一个扩展属性totalDataSize
。这些扩展函数和属性可以直接在类的实例上使用。
请注意以下事项:
- 在类内部定义的扩展函数和属性可以直接访问类的私有成员。
- 在类内部定义的扩展函数和属性只在类的实例上可见,不能在类的伴随对象或其他作用域中访问。
通过在类内部使用扩展函数和属性,你可以为类添加特定的功能,与类的其他成员一起工作,并直接在类的实例上使用它们。这样可以提高代码的可读性和可维护性,并使类的功能更加灵活。
1.2.8 调用特定类的成员函数
在Kotlin中,你可以使用限定符(qualifier)来调用特定类的成员函数。通过限定符,你可以明确指定调用哪个类的成员函数,尤其在存在函数名称冲突或多个类具有相同函数名时很有用。以下是几种使用限定符调用特定类成员函数的方法:
-
使用类名作为限定符:
class MyClass { fun myFunction() { println("Hello from MyClass") } } fun main() { val obj = MyClass() obj.myFunction() // 调用MyClass的myFunction函数 }
在这个例子中,我们通过创建
MyClass
的实例obj
来调用它的成员函数myFunction()
。通过实例对象限定符,我们明确地指定了调用的是MyClass
的成员函数。 -
使用扩展函数:
class MyClass { fun myFunction() { println("Hello from MyClass") } } fun MyClass.anotherFunction() { println("Hello from extension function") } fun main() { val obj = MyClass() obj.myFunction() // 调用MyClass的myFunction函数 obj.anotherFunction() // 调用MyClass的扩展函数 }
在这个例子中,我们在
MyClass
类外部定义了一个扩展函数anotherFunction()
。通过在实例对象上调用该扩展函数,我们仍然是明确指定了要调用的是MyClass
的函数。
使用限定符的好处是可以避免函数名称冲突,并确保在存在多个类具有相同函数名的情况下,调用的是正确的类的函数。
需要注意的是,限定符只能用于调用类的成员函数,无法用于调用类的扩展函数。扩展函数的调用需要使用扩展函数的定义作用域来限定,而不是使用限定符。
1.2.9 扩展成员的继承
在Kotlin中,扩展函数和属性是静态解析的,它们不会被子类继承。这意味着在子类中定义相同名称的函数或属性不会覆盖或继承父类的扩展函数或属性。相反,子类会拥有自己独立的同名成员函数或属性。
让我们看一个示例来说明这一点:
open class Parent
fun Parent.printMessage() {
println("Hello from Parent")
}
class Child : Parent()
fun Child.printMessage() {
println("Hello from Child")
}
fun main() {
val parent: Parent = Child()
parent.printMessage() // 输出: Hello from Parent
}
在这个例子中,我们有一个Parent
类和一个Child
类,Child
类继承自Parent
类。我们在Parent
类中定义了一个扩展函数printMessage()
,然后在Child
类中定义了一个同名的函数。
当我们通过Child
类的实例来调用printMessage()
函数时,实际上调用的是Parent
类的扩展函数。这是因为扩展函数是静态解析的,它们的调用在编译时就确定了,而不是在运行时根据实际对象的类型进行动态分派。
因此,子类无法继承或覆盖父类的扩展函数或属性。扩展函数和属性仅在定义它们的作用域内可见,并且它们不能通过继承传递给子类。