Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

接口

接口 描述了一种类型的概念。它是所有实现该接口的类的原型。

它描述了一个类应该做什么,但不涉及它应该如何去做。接口提供了一个形式,但通常不提供具体实现。它规定了对象的操作,但不详细说明这些操作是如何执行的。接口描述了实体的任务或目标,而不是包含实现细节的类。

一个字典的定义说,接口是“独立而常常不相关的系统相遇并相互作用或相互通信的地方”。因此,接口是系统不同部分之间通信的一种方式。

一个 应用程序编程接口(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 属性:

  • Food 直接替换了 symbol 的值。
  • Robot 具有一个返回该值的自定义 getter(请参阅属性访问器)。
  • Wall 在构造函数参数列表中覆盖了 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 找到。