接口
接口 描述了一种类型的概念。它是所有实现该接口的类的原型。
它描述了一个类应该做什么,但不涉及它应该如何去做。接口提供了一个形式,但通常不提供具体实现。它规定了对象的操作,但不详细说明这些操作是如何执行的。接口描述了实体的任务或目标,而不是包含实现细节的类。
一个字典的定义说,接口是“独立而常常不相关的系统相遇并相互作用或相互通信的地方”。因此,接口是系统不同部分之间通信的一种方式。
一个 应用程序编程接口(API)是各种软件组件之间明确定义的通信路径的集合。在面向对象编程中,对象的 API 是一组它用于与其他对象交互的公共成员。
使用特定接口的代码只知道可以为该接口调用哪些函数。接口在类之间建立了一个“协议”。(某些面向对象的语言有一个称为 protocol 的关键字来执行同样的操作。)
要创建一个接口,使用 interface
关键字而不是 class
关键字。在定义实现接口的类时,将类名与 :
(冒号)和接口的名称连接起来:
// Interfaces/Computer.kt
package interfaces
import atomictest.*
interface Computer {
fun prompt(): String
fun calculateAnswer(): Int
}
class Desktop : Computer {
override fun prompt() = "Hello!"
override fun calculateAnswer() = 11
}
class DeepThought : Computer {
override fun prompt() = "Thinking..."
override fun calculateAnswer() = 42
}
class Quantum : Computer {
override fun prompt() = "Probably..."
override fun calculateAnswer() = -1
}
fun main() {
val computers = listOf(
Desktop(), DeepThought(), Quantum()
)
computers.map { it.calculateAnswer() } eq
"[11, 42, -1]"
computers.map { it.prompt() } eq
"[Hello!, Thinking..., Probably...]"
}
Computer
声明 了 prompt()
和 calculateAnswer()
,但没有提供实现。实现接口的类必须为所有声明的函数提供函数体,使这些函数变为 具体 函数。在 main()
中,您可以看到实现接口的不同类通过其函数定义来表达不同的行为。
当实现接口的成员时,必须使用 override
修饰符。override
告诉 Kotlin,您有意使用了接口(或基类)中出现的相同名称,即您不是意外覆盖了它。
接口可以声明属性。这些属性必须在实现该接口的所有类中进行覆盖:
// Interfaces/PlayerInterface.kt
package interfaces
import atomictest.eq
interface Player {
val symbol: Char
}
class Food : Player {
override val symbol = '.'
}
class Robot : Player {
override val symbol get() = 'R'
}
class Wall(override val symbol: Char) : Player
fun main() {
listOf(Food(), Robot(), Wall('|')).map {
it.symbol
} eq "[., R, |]"
}
每个子类以不同的方式覆盖 symbol
属性:
枚举可以实现一个 interface
:
// Interfaces/Hotness.kt
package interfaces
import atomictest.*
interface Hotness {
fun feedback(): String
}
enum class SpiceLevel : Hotness {
Mild {
override fun feedback() =
"It adds flavor!"
},
Medium {
override fun feedback() =
"Is it warm in here?"
},
Hot {
override fun feedback() =
"I'm suddenly sweating a lot."
},
Flaming {
override fun feedback() =
"I'm in pain. I am suffering."
}
}
fun main() {
SpiceLevel.values().map { it.feedback() } eq
"[It adds flavor!, " +
"Is it warm in here?, " +
"I'm suddenly sweating a lot., " +
"I'm in pain. I am suffering.]"
}
编译器确保每个 enum
元素为 feedback()
提供了一个定义。
SAM 转换
Single Abstract Method(SAM)接口来自 Java,其中称成员函数为 “方法”。Kotlin 有一种特殊的语法来定义 SAM 接口:fun interface
。在这里,我们展示具有不同参数列表的 SAM 接口:
// Interfaces/SAM.kt
package interfaces
fun interface ZeroArg {
fun f(): Int
}
fun interface OneArg {
fun g(n: Int): Int
}
fun interface TwoArg {
fun h(i: Int, j: Int): Int
}
当使用 fun interface
时,编译器确保只有一个单一的成员函数。
您可以以普通冗长的方式实现 SAM 接口,也可以通过传递 lambda 来实现它;后者称为 SAM 转换。在 SAM 转换中,lambda 变成了接口中单一方法的实现。在这里,我们展示了实现三个接口的两种方式:
// Interfaces/SAMImplementation.kt
package interfaces
import atomictest.eq
class VerboseZero : ZeroArg {
override fun f() = 11
}
val verboseZero = VerboseZero()
val samZero = ZeroArg { 11 }
class VerboseOne : OneArg {
override fun g(n: Int) = n + 47
}
val verboseOne = VerboseOne()
val samOne = OneArg { it + 47 }
class VerboseTwo : TwoArg {
override fun h(i: Int, j: Int) = i + j
}
val verboseTwo = VerboseTwo()
val samTwo = TwoArg { i, j -> i + j }
fun main() {
verboseZero.f() eq 11
samZero.f() eq 11
verboseOne.g(92) eq 139
samOne.g(92) eq 139
verboseTwo.h(11, 47) eq 58
samTwo.h(11, 47) eq 58
}
通过比较 “verbose” 实现和 “sam” 实现,您可以看到 SAM 转换为一个常用的习惯用法产生了更简洁的语法,您不必强制定义一个类来创建一个单一的对象。
您可以将 lambda 传递给期望 SAM 接口的地方,而不必首先将其包装为对象:
// Interfaces/SAMConversion.kt
package interfaces
import atomictest.trace
fun interface Action {
fun act()
}
fun delayAction(action: Action) {
trace("Delaying...")
action.act()
}
fun main() {
delayAction { trace("Hey!") }
trace eq "Delaying... Hey!"
}
在 main()
中,我们传递了一个 lambda,而不是实现了 Action
interface
的对象。Kotlin 会自动从该 lambda 创建一个 Action
对象。
练习和解答可以在 www.AtomicKotlin.com 找到。