Post

Scala基础教程 第4节 特质和抽象类

Scala基础教程 第4节 特质和抽象类

https://docs.scala-lang.org/overviews/scala-book/traits-intro.html

Scala 特质(trait)是该语言的一大特点。可以像Java接口一样使用,也可以像抽象类一样使用。Scala类还可以扩展和“混入”(mix in)多个特质。

Scala也有抽象类的概念,我们将展示何时应该使用抽象类而不是特质。

4.1 特质用作接口

https://docs.scala-lang.org/overviews/scala-book/traits-interfaces.html

使用Scala特质的一种方式是类似于Java接口,即为某些功能定义需要的接口,但不实现任何行为。

4.1.1 定义特质

在Scala中使用关键字trait定义特质。例如:

1
2
3
trait Speaker {
  def speak(): String
}

这段代码声明了一个名为Speaker的特质,包含一个没有参数、返回String的方法speak()。该方法没有方法体,因此是抽象方法。任何扩展该特质的类都必须实现speak()方法。这段代码等价于以下Java接口:

1
2
3
public interface Speaker {
    String speak();
}

注:在Scala 2中,特质不能有构造器参数,但可以有成员变量。

4.1.2 扩展特质

使用关键字extends创建扩展单个特质的类。可以像这样编写两个类CatDog,扩展特质Speaker并实现其中的方法:

1
2
3
4
5
6
7
class Cat extends Speaker {
  override def speak(): String = "Meow~"
}

class Dog extends Speaker {
  override def speak(): String = "Woof!"
}

关键字override等价于Java的@Override注解。不是强制的,但能使编译器检查方法签名与特质/超类方法的一致性,因此建议加上。

可以在REPL中测试代码:

1
2
3
4
5
6
def speak(s: Speaker): Unit = println(s.speak())

val c = new Cat
val d = new Dog
speak(c) // prints "Meow~"
speak(d) // prints "Woof!"

4.1.3 扩展多个特质

Scala特质使你能够创建非常模块化的代码。例如,可以将动物的属性分解为小的模块化单元:

1
2
3
4
5
6
7
8
9
10
11
12
13
trait Speaker {
  def speak(): String
}

trait TailWagger {
  def startTail(): Unit
  def stopTail(): Unit
}

trait Runner {
  def startRunning(): Unit
  def stopRunning(): Unit
}

现在可以创建一个Dog类扩展所有特质并实现必要的方法:

1
2
3
4
5
6
7
8
9
10
11
12
class Dog extends Speaker with TailWagger with Runner {
  // Speaker
  override def speak(): String = "Woof!"

  // TailWagger
  override def startTail(): Unit = println("tail is wagging")
  override def stopTail(): Unit = println("tail is stopped")

  // Runner
  override def startRunning(): Unit = println("I'm running")
  override def stopRunning(): Unit = println("Stopped running")
}

注意,对于第一个特质使用extends,对于后续特质使用with

4.2 特质用作抽象类

https://docs.scala-lang.org/overviews/scala-book/traits-abstract-mixins.html

还可以向特质中添加具体(非抽象)方法,并将其用作抽象类(更准确地说是混入(mix-in))。

4.2.1 示例

下面的特质Pet有一个具体方法speak()和一个抽象方法comeToMaster()

1
2
3
4
trait Pet {
  def speak(): Unit = println("Yo")  // concrete method
  def comeToMaster(): Unit           // abstract method
}

当一个类扩展一个特质时,必须实现所有抽象方法。但不必重新定义具体方法,除非你想覆盖它。下面的类Dog扩展了Pet

1
2
3
class Dog(name: String) extends Pet {
  override def comeToMaster(): Unit = println("Woo-hoo, I'm coming!")
}
1
2
3
val d = new Dog("Zeus")
d.speak() // prints "Yo"
d.comeToMaster() // prints "Woo-hoo, I'm coming!"

4.2.2 覆盖方法

类也可以覆盖特质中定义的方法。例如,类Cat覆盖了speak()方法:

1
2
3
4
5
class Cat extends Pet {
  // override 'speak'
  override def speak(): Unit = println("Meow")
  override def comeToMaster(): Unit = println("That's not gonna happen.")
}
1
2
3
val c = new Cat
c.speak() // prints "Meow"
c.comeToMaster() // prints "That's not gonna happen."

4.2.3 混入多个特质

Scala特质的一个优点是可以将多个具有行为(即具体方法)的特质混入到类中。例如,下面有一组特质,其中一个定义了抽象方法,其他定义了具体方法实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
trait Speaker {
  def speak(): String   // abstract
}

trait TailWagger {
  def startTail(): Unit = println("tail is wagging")
  def stopTail(): Unit = println("tail is stopped")
}

trait Runner {
  def startRunning(): Unit = println("I'm running")
  def stopRunning(): Unit = println("Stopped running")
}

可以像这样创建DogCat类并扩展所有这些特质:

1
2
3
4
5
6
7
8
9
class Dog(name: String) extends Speaker with TailWagger with Runner {
  override def speak(): String = "Woof!"
}

class Cat extends Speaker with TailWagger with Runner {
  override def speak(): String = "Meow~"
  override def startRunning(): Unit = println("Yeah ... I don't run")
  override def stopRunning(): Unit = println("No need to stop")
}

注:当一个Scala类的超类和特质具有相同的方法时,解决冲突的方式与Java一样,详见《Java核心技术》笔记 卷I 第6章 6.1.6节。

4.2.4 创建实例时混入特质

对于具有具体方法的特质,可以在创建实例时将其混入到类中。例如,对于上一节中的TailWaggerRunner特质和以下Dog类:

1
class Dog(name: String)

可以像这样创建Dog实例:

1
2
3
val d = new Dog("Fido") with TailWagger with Runner
d.startTail()  // prints "tail is wagging"
d.startRunning()  // prints "I'm running"

注:d的实际类型是一个匿名类,扩展了Dog类和TailWaggerRunner特质。

不能用这种方式混入包含抽象方法的特质,除非提供实现。例如:

1
2
3
val d = new Dog("Fido") with Speaker {
  override def speak(): String = "Woof!"
}

4.3 抽象类

https://docs.scala-lang.org/overviews/scala-book/abstract-classes.html

Scala还有抽象类(abstract class)的概念,类似于Java的抽象类。但由于特质非常强大,只有在以下情况下才需要使用抽象类:

  • 你希望创建一个有构造器参数的超类。
  • 你的Scala代码会被Java代码调用。

4.3.1 定义抽象类

抽象类语法类似于普通类,只是多了关键字abstract。例如,下面是一个名为Pet的抽象类,类似于4.2.1节定义的Pet特质:

1
2
3
4
abstract class Pet(name: String) {
  def speak(): Unit = println("Yo")  // concrete method
  def comeToMaster(): Unit           // abstract method
}

可以像这样定义扩展该抽象类的Dog类:

1
2
3
4
class Dog(name: String) extends Pet(name) {
  override def speak(): Unit = println("Woof!")
  override def comeToMaster(): Unit = println("Here I come!")
}

需要注意的一点是构造器参数name是如何从Dog构造器传递到Pet构造器的。

一个类只能扩展一个抽象类,但可以扩展多个特质。

4.3.2 在Java代码中调用

Java类可以扩展Scala抽象类。例如,对于上一节中的抽象类Pet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Dog extends Pet {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void speak() {
        System.out.println("Woof!");
    }

    @Override
    public void comeToMaster() {
        System.out.println("Here I come!");
    }
}

Java类也可以通过implements关键字实现Scala特质(就像Java接口一样)。例如,对于4.2.1节中的特质Pet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Dog implements Pet {
    private String name;

    public Dog(String name) {
        this.name = name;
    }

    @Override
    public void speak() {
        System.out.println("Woof!");
    }

    @Override
    public void comeToMaster() {
        System.out.println("Here I come!");
    }
}

4.4 抽象类型成员

https://docs.scala-lang.org/tour/abstract-type-members.html

抽象类型(如特质和抽象类)可以有抽象类型成员(abstract type member),由实现类定义实际的类型。例如:

1
2
3
4
trait Buffer {
  type T
  val element: T
}

特质Buffer有一个抽象类型成员T,用于描述element的类型。可以在抽象类中扩展这个特质,并为T添加类型上界,使其更具体。

1
2
3
4
5
6
7
8
9
abstract class SeqBuffer extends Buffer {
  type U
  type T <: Seq[U]
  def length: Int = element.length
}

abstract class IntSeqBuffer extends SeqBuffer {
    type U = Int
}

SeqBuffer声明T的类型上界(<:)是另一个抽象类型U的序列,即T必须是Seq[U]的子类型。这意味着只能在element中存储序列,因此可以调用element.length

IntSeqBuffer定义了成员类型U = Int,但成员类型T和成员变量element仍然是抽象的。

具有抽象类型成员的特质或类通常与匿名类实例化结合使用。例如,下面的程序创建了引用整数列表的IntSeqBuffer

1
2
3
4
5
6
7
8
def newIntSeqBuf(elems: Int*): IntSeqBuffer = new IntSeqBuffer {
  type T = List[U]
  val element = List(elems: _*)
}

val buf = newIntSeqBuf(7, 8)
println("length = " + buf.length)   // length = 2
println("content = " + buf.element) // content = List(7, 8)

其中,工厂方法newIntSeqBuf()使用IntSeqBuffer的匿名子类,将抽象类型T设置为具体类型List[Int]

也可以将抽象类型成员转换为泛型类的类型参数,反之亦然。以下是上面的代码只使用类型参数的版本:

1
2
3
4
5
6
7
abstract class Buffer[+T] {
  val element: T
}

abstract class SeqBuffer[U, +T <: Seq[U]] extends Buffer[T] {
  def length: Int = element.length
}
1
2
3
4
5
6
7
def newIntSeqBuf(elems: Int*): SeqBuffer[Int, Seq[Int]] = new SeqBuffer[Int, List[Int]] {
  val element = List(elems: _*)
}

val buf = newIntSeqBuf(7, 8)
println("length = " + buf.length)
println("content = " + buf.element)

注意,在这里使用了协变类型(+T <: Seq[U])来隐藏方法newIntSeqBuf()返回的对象的具体类型。

4.5 self类型

https://docs.scala-lang.org/tour/self-types.html

Self类型(self-type)是一种声明特质A必须混入到另一个特质B中的方式,即使B没有直接扩展A(换句话说,扩展特质B的类必须同时扩展特质A)。这使得B可以直接访问A的成员而无需导入。

Self类型是一种缩窄this(或其别名标识符)的类型的方式。要在特质中使用self类型,使用语法someIdentifier: SomeOtherTrait =>,其中someIdentifierthis的别名标识符,SomeOtherTrait是要混入的特质。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
trait User {
  def username: String
}

trait Tweeter {
  this: User =>  // self-type

  def tweet(tweetText: String): Unit = println(s"$username: $tweetText")
}

class VerifiedTweeter(val username_ : String) extends Tweeter with User {  // We mixin User because Tweeter required it
  override 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"

因为特质Tweeter声明了this: User =>,因此tweet()方法可以直接访问User的方法username。这也意味着由于VerifiedTweeter扩展了Tweeter,它必须同时扩展User

This post is licensed under CC BY 4.0 by the author.