我们可以用 this 来指代调用该方法的实例。一般情况下,我们不需要显示地使用 this,但如果作用域内存在同名变量时,显示使用 this 有助于消除二义性。此时就需要使用
自类型标记 (self-type annotation )
而有时我们想处理任意带有某方法的实例,此时就要用到
结构化类型(Structural type)
自类型标记 (self-type annotation )
可以帮助我们完成两件事:
- 创建 this 的别名
- 声明某特征/类必须引入另一特征/类
先来看第一点,自类型标记可以起到为 this 起别名的功能:
// 下文的 self v 并不是关键字,可以把它换成任何合法的关键字
class C1 { self =>
def talk(message: String) = println(s"C1 talk: $message")
class C2 {
class C3 {
def talk(message: String) = self.talk(s"C3 talk: $message")
}
}
}
val c1 = new C1
cl.talk("Hello") // C1 talk: Hello
cl.c2.c3.talk("Hello") // C1.talk: C3.talk: World
如果没有自类型标记,我们就无法直接从 C3.talk 中调用 C1 talk,因为二者名称相同,后者将屏蔽前者,而且 C3 也不是 C1 的直接子类,所以也不能调用 super.talk。
可以将其认为是一个指向固定位置的 this 引用。
功能二:声明某特征/类必须引入另一特征/类
trait User {
def username: String
}
trait Tweeter {
// 注意这一行
// 这一行将 this 的类型指定为 User
// 此时这个 trait 相当于变成了 User 的子特征,可以使用 User 中的元素了
this: User =>
def tweet(tweetText: String) = println(s"$username: $tweetText")
}
// 但同时,使用 Tweeter 这个特征就必须同时引入 User 这个特征
class VerifiedTweeter(val username_ : String) extends Tweeter with User {
def username = s"real $username_"
}
val realBeyoncé = new VerifiedTweeter("Beyoncé")
realBeyoncé.tweet("Just spilled my glass of lemonade") // prints "real Beyoncé: Just spilled my glass of lemonade"
自类型标记可以将一个特征/类 A 指定为另一个特征/类 B 的子特征/类,从而使用 B 中对应的功能,但引入这个被修改的特征/类时就必须同时引入 B 类。同理,此处的 this 关键字也可以替换成其他名称。
“如果它走起来像鸭子,并且跟鸭子一样呱呱叫,那么它一定是鸭子。” —— 鸭子类型
结构化类型(Structural type)
可以当做一种类型安全的鸭子类型(duck typing)
。鸭子类型是动态类型语言中一种方法解析方式,例如:在 Ruby 中如果你的代码包含有列表相关的操作时,即使他不是真正的列表,也能被当做列表使用。换句话说,解释器并不知道这个实例是否是列表的实例,但他会遵循各种规则来寻找方法以供调用,或在找不到方法时对失败进行处理。
Scala 不支持这种运行时的方法解析(虽然有一个例外待补充),不过,Scala 有一个类型的机制,他在编译时进行处理。Scala 允许你指定的对象必须符合某种特定的结构:
指名类型(nominal typing)
示例代码如下:
object ScJs {
// 像这样指定类型
def letsQuack(duck: {def quack(): String}): Unit = {
println(duck.quack())
}
// 以3个不同的实现类为例
class SimpleDuck {
def quack(): String = "Quack"
}
class MachineDuck {
def quack(): String = "q-u-a-c-k"
}
// 假设这是一个只有 quack 方法实现,而其他完全不同的类
class Synthesizer {
def quack(): String = "Output> quack"
}
def main(args: Array[String]): Unit = {
letsQuack(new SimpleDuck) // Quack
letsQuack(new MachineDuck) // q-u-a-c-k
letsQuack(new Synthesizer) // Output> quack
}
}
即使类 Synthesizer 已经与鸭子毫无关系了(举例),我们依然可以正常执行方法并获得输出。我们在指定抽象类型时,一样可采用这种声明方式。
trait QuackLike {
// 因为下文缘故,此处必须开启反射调用
import scala.language.reflectiveCalls
type Sound
// 不过由于 Scala 不会让结构化类型 指向抽象类型或类型参数,所以下文中的 state 必须指定一个已经存在的类型
// (意为:虽然你能嘎嘎叫,但你可能不是鸭子,因此我无法执行除了嘎嘎叫外的其他方法,需要强制指明这是鸭子之后才可以)
type quack = {def quack(sound: Any): String}
type quack = {def quack(sound: Sound): String} // ❌编译错误
// 测试代码
def letsQuack(duck: quack, sound: Sound): Unit = {
println(duck.quack(sound))
}
}
trait QuackLike {
type Sound
// 这是另一种声明方式,将需要调用的方法起别名,这样在调用时只需要关心方法而不需要关心类型
// 在你只关心方法时这种更加实用,而且函数的类型也不需要强行指定为 Any 了
type quack = Sound => String
def letsQuack(quack: quack, sound: Sound): Unit = {
println(quack(sound))
}
}