Kotlin学习(三):枚举类和扩展
枚举类
枚举类基本用法
在 Kotlin 中,枚举类型是以类的形式存在的,因此称为枚举类。下面的代码是最简单的枚举类定义方式。
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
每个枚举常量都是一个对象。枚举常量用逗号分隔。
那么如何使用枚举类呢?既然枚举类也是类,那么自然可以按使用类的方式使用枚举类,使用类的方式无外乎定义变量、初始化、判断变量是否相等这几样操作。下面的代码演示了枚举类是如何使用的:
// 定义一个枚举类变量
var directionl: Direction
// 定义一个枚举类变量,并初始化
var direction2: Direction = Direction .NORTH
// 未指定数据类型,通过右侧的赋值 自动检测类型
var direction3 = Direction.EAST
// 未指定数据类型,通过右侧的赋值自动检测类型
var direction4 = Direction.EAST
// 判断两个枚举类型变量的值是否相等
if(direction3 == direction4) {
println("枚举类型值相等")
}else {
println("枚举类型值不相等")
}
上面代码输出结果:枚举类型值相等。
需要注意的是:引用枚类 的值, 需要加上枚举类名,不能直接写成 EAST、WEST 样式。现在我们来做个试验,输出枚举类中的一个值,看看会得到什么结果。
println(Direction.EAST) // 输出EAST
println(Direction.WEST) // 输出WEST
结果很明显,在默认状态下,直接输出枚举类的元素值,会输出元素值名称。
为枚举值指定对应的数值
枚举类每一个值就是当前枚举类的对象,因此,如果要为每一个枚举类的值指定一个数字,直接通过构造函数传入即可:
enum class Direction private constructor(val d: Int) {
// 通过 Direction 的构造器传入枚举值对应的值
NORTH(1), SOUTH(2), WEST(3), EAST(4);
// 返回当前枚举类对应的数字
override fun toString(): String {
return d.toString()
}
}
匿名类
枚举常量还可以声明其带有相应方法以及覆盖了基类方法的匿名类。
enum class ProtocolState {
WAITING {
override fun signal() = TALKING
},
TALKING {
override fun signal() = WAITING
};
abstract fun signal(): ProtocolState
}
如果枚举类定义任何成员,那么使用分号将成员定义中的枚举常量定义分隔开。
枚举条目不能包含内部类以外的嵌套类型。
在枚举类中实现接口
一个枚举类可以实现接口(但不能从类继承),可以为所有条目提供统一的接口成员实现,也可以在相应匿名类中为每个条目提供各自的实现。只需将接口添加到枚举类声明中即可,如下所示:
enum class IntArithmetics : BinaryOperator<Int>, IntBinaryOperator {
PLUS {
override fun apply(t: Int, u: Int): Int = t + u
},
TIMES {
override fun apply(t: Int, u: Int): Int = t * u
};
override fun applyAsInt(t: Int, u: Int) = apply(t, u)
}
fun main() {
val a = 13
val b = 31
for (f in IntArithmetics.values()) {
println("$f($a, $b) = ${
f.apply(a, b)}")
}
}
使用枚举常量
Kotlin 中的枚举类也有合成方法允许列出定义的枚举常量以及通过名称获取枚举常量。这些方法的签名如下(假设枚举类的名称是 EnumClass
):
EnumClass.valueOf(value: String): EnumClass
EnumClass.values(): Array<EnumClass>
如果指定的名称与类中定义的任何枚举常量均不匹配,valueOf()
方法将抛出 IllegalArgumentException
异常。
自 Kotlin 1.1 起,可以使用 enumValues<T>()
与 enumValueOf<T>()
函数以泛型的方式访问枚举类中的常量 :
enum class RGB {
RED, GREEN, BLUE }
inline fun <reified T : Enum<T>> printAllValues() {
print(enumValues<T>().joinToString {
it.name })
}
printAllValues<RGB>() // 输出 RED, GREEN, BLUE
每个枚举常量都具有在枚举类声明中获取其名称与位置的属性:
val name: String
val ordinal: Int
如果想获取枚举类中所有枚举值所对应的数值,可以使用 values 方法,代码如下:
enum class Direction private constructor(val d: Int) {
// 通过 Direction 的构造器传入枚举值对应的值
NORTH(1), SOUTH(2), WEST(3), EAST(4);
// 返回当前枚举类对应的数字
override fun toString(): String {
return d.toString()
}
}
fun main() {
for (d in Direction.values()) {
println("name = [ ${
d.name} ] , ordinal = [ ${
d.ordinal} ] , d = [ $d ]")
}
}
// 输出结果:
// name = [ NORTH ] , ordinal = [ 0 ] , d = [ 1 ]
// name = [ SOUTH ] , ordinal = [ 1 ] , d = [ 2 ]
// name = [ WEST ] , ordinal = [ 2 ] , d = [ 3 ]
// name = [ EAST ] , ordinal = [ 3 ] , d = [ 4 ]
扩展
Kotlin 能够扩展一个类的新功能而无需继承该类或者使用像装饰者这样的设计模式。 这通过叫做 扩展 的特殊声明完成。 例如,你可以为一个你不能修改的、来自第三方库中的类编写一个新的函数。 这个新增的函数就像那个原始类本来就有的函数一样,可以用普通的方法调用。 这种机制称为 扩展函数 。此外,也有 扩展属性 , 允许你为一个已经存在的类添加新的属性。
扩展函数
声明一个扩展函数,我们需要用一个 接收者类型 也就是被扩展的类型来作为他的前缀。 下面代码为 MutableList<Int>
添加一个swap
函数:
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // “this”对应该列表
this[index1] = this[index2]
this[index2] = tmp
}
这段代码放到哪个 Kotlin 文件都可以,一般会放到 Kotlin 文件顶层,当然,也可以放在调用 swap 方法的位置的前面。
这个 this 关键字在扩展函数内部对应到接收者对象(传过来的在点符号前的对象) 现在,我们对任意 MutableList<Int>
调用该函数了:
val list = mutableListOf(1, 2, 3)
list.swap(0, 2) // “swap()”内部的“this”会保存“list”的值
当然,这个函数对任何 MutableList<T>
起作用,我们可以泛化它:
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // “this”对应该列表
this[index1] = this[index2]
this[index2] = tmp
}
为了在接收者类型表达式中使用泛型,我们要在函数名前声明泛型参数。
扩展是静态解析的
扩展不能真正的修改他们所扩展的类。通过定义一个扩展,你并没有在一个类中插入新成员, 仅仅是可以通过该类型的变量用点表达式去调用这个新函数。
我们想强调的是扩展函数是静态分发的,即他们不是根据接收者类型的虚方法。 这意味着调用的扩展函数是由函数调用所在的表达式的类型来决定的, 而不是由表达式运行时求值结果决定的。例如:
fun main() {
open class Shape
class Rectangle: Shape()
fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"
fun printClassName(s: Shape) {
println(s.getName())
}
printClassName(Rectangle())
}
这个例子会输出 “Shape”,因为调用的扩展函数只取决于参数 s
的声明类型,该类型是 Shape
类。
扩展函数
如果一个类定义有一个成员函数与一个扩展函数,而这两个函数又有相同的接收者类型、 相同的名字,并且都适用给定的参数,这种情况总是取成员函数。 例如:
class Example {
// 成员函数
fun printFunctionType() {
println("Class method")
}
}
// 扩展函数
fun Example.printFunctionType() {
println("Extension function")
}
fun main() {
Example().printFunctionType()
}
打印结果:Class method
当然,扩展函数重载同样名字但不同签名成员函数也完全可以:
class Example {
// 成员函数
fun printFunctionType() {
println("Class method")
}
}
// 扩展函数
fun Example.printFunctionType() {
println("Extension function")
}
// 扩展函数重载
fun Example.printFunctionType(num: Int) {
println("Extension function : $num")
}
fun main() {
Example().printFunctionType(1)
}
打印结果:Extension function : 1
很明显,如果类内部的成员函数和通过扩展添加的成员函数冲突,那么内部成员函数的优先级更高,因此,通过扩展无法覆盖内部成员函数。
扩展属性
扩展属性和扩展函数类似, Kotlin 属性在类中必须初始化,而初始化需要使用 backing field, 也就是那个 field 字段,可以将属性设置的值保存在 field 中,也可以从 field 获得属性值。不过,通过扩展添加的属性是没有 backing field 的,因此,扩展属性需要实先 setter 部分,这样才能为扩展属性初始化。
class Example {
// 需要声明为 public,或不加修饰符( 默认是 public ),否则扩展属性无法访问该变量
var mValue: Int = 0
// 内部属性
var str: String = ""
get() = field
set(value) {
field = value
}
}
// 扩展属性,需要实现 setter 部分
var Example.value: Int
get() = mValue
set(value) {
mValue = value
}
由于扩展属性没有 backing field 字段,因此保存和获取属性值,需要使用一个类成员变量。但成员变量需要声明为 public ,否则扩展属性无法访问。
扩展伴生对象
如果一个类定义有一个伴生对象,你也可以为伴生对象定义扩展函数与属性。就像伴生对象的常规成员一样, 可以只使用类名作为限定符来调用伴生对象的扩展成员:
class MyClass {
companion object {
} // 将被称为 "Companion"
}
fun MyClass.Companion.printCompanion() {
println("companion")
}
fun main() {
MyClass.printCompanion() // 与调用静态成员函数一样,不需要使用类的实例
}
扩展的范围
大多数时候我们在顶层定义扩展——直接在包里:
package org.example.declarations
fun List<String>.getLongestString() {
...
}
要使用所定义包之外的一个扩展,我们需要在调用方导入它:
package org.example.usage
import org.example.declarations.getLongestString
fun main() {
val list = listOf("red", "green", "blue")
list.getLongestString()
}
扩展声明为成员
在一个类内部你可以为另一个类声明扩展。在这样的扩展内部,有多个 隐式接收者 —— 其中的对象成员可以无需通过限定符访问。扩展声明所在的类的实例称为 分发接收者,扩展方法调用所在的接收者类型的实例称为 扩展接收者 。
class Host(val hostname: String) {
fun printHostname() {
print(hostname)
}
}
class Connection(val host: Host, val port: Int) {
fun printPort() {
print(port)
}
fun Host.printConnectionString() {
printHostname() // 调用 Host.printHostname()
print(":")
printPort() // 调用 Connection.printPort()
}
fun connect() {
...
host.printConnectionString() // 调用扩展函数
}
}
fun main() {
Connection(Host("kotl.in"), 443).connect()
//Host("kotl.in").printConnectionString(443) // 错误,该扩展函数在 Connection 外不可用
}
对于分发接收者与扩展接收者的成员名字冲突的情况,扩展接收者优先。要引用分发接收者的成员你可以使用 限定的 this
语法。
class Connection {
fun Host.getConnectionString() {
toString() // 调用 Host.toString()
this@Connection.toString() // 调用 Connection.toString()
}
}
声明为成员的扩展可以声明为 open
并在子类中覆盖。这意味着这些函数的分发对于分发接收者类型是虚拟的,但对于扩展接收者类型是静态的。
open class Base {
}
class Derived : Base() {
}
open class BaseCaller {
open fun Base.printFunctionInfo() {
println("Base extension function in BaseCaller")
}
open fun Derived.printFunctionInfo() {
println("Derived extension function in BaseCaller")
}
fun call(b: Base) {
b.printFunctionInfo() // 调用扩展函数
}
}
class DerivedCaller: BaseCaller() {
override fun Base.printFunctionInfo() {
println("Base extension function in DerivedCaller")
}
override fun Derived.printFunctionInfo() {
println("Derived extension function in DerivedCaller")
}
}
fun main() {
BaseCaller().call(Base()) // “Base extension function in BaseCaller”
DerivedCaller().call(Base()) // “Base extension function in DerivedCaller”——分发接收者虚拟解析
DerivedCaller().call(Derived()) // “Base extension function in DerivedCaller”——扩展接收者静态解析
}
关于可见性的说明
扩展的可见性与相同作用域内声明的其他实体的可见性相同。例如:
- 在文件顶层声明的扩展可以访问同一文件中的其他
private
顶层声明; - 如果扩展是在其接收者类型外部声明的,那么该扩展不能访问接收者的
private
成员。