Scala学习之TRAITS和抽象类

Posted 顧棟

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Scala学习之TRAITS和抽象类相关的知识,希望对你有一定的参考价值。

SCALA TRAITS AND ABSTRACT CLASSES

原文地址:https://docs.scala-lang.org/overviews/scala-book/traits-intro.html

Scala trait是该语言的一个重要特性。 正如您将在接下来的课程中看到的,您可以像使用 Java interface一样使用它们,也可以像使用具有实际方法的抽象类一样使用它们。 Scala 类还可以扩展和“混合”多个特征。

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

Using Scala Traits as Interfaces

原文地址:https://docs.scala-lang.org/overviews/scala-book/traits-interfaces.html

使用Scala trait的一种方法类似于原始Java接口,在该接口中为某些功能定义所需的接口,但不实现任何行为。

A simple example

作为让我们开始的示例,假设您想编写一些代码来模拟狗和猫等任何有尾巴的动物。 在 Scala 中,您可以编写一个 trait 来启动建模过程,如下所示:

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

该代码声明了一个名为 TailWagger 的 trait,它声明任何扩展 TailWagger 的类都应该实现 startTailstopTail方法。 这两种方法都没有输入参数,也没有返回值。 此代码等效于此 Java 接口:

public interface TailWagger {
    public void startTail();
    public void stopTail();
}

Extending a trait

给出一個trait

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

您可以编写一个扩展 trait 并实现这些方法的类,如下所示:

class Dog extends TailWagger {
    // the implemented methods
    def startTail(): Unit = println("tail is wagging")
    def stopTail(): Unit = println("tail is stopped")
}

如果您愿意,您也可以像这样编写这些方法:

class Dog extends TailWagger {
    def startTail() = println("tail is wagging")
    def stopTail() = println("tail is stopped")
}

请注意,在任何一种情况下,您都使用 extends 关键字来创建一个扩展单个 trait 的类:

class Dog extends TailWagger { ...
          -------

如果您将 TailWagger trait 和 Dog 类粘贴到 Scala REPL 中,您可以像这样测试代码:

scala> val d = new Dog
d: Dog = Dog@234e9716

scala> d.startTail
tail is wagging

scala> d.stopTail
tail is stopped

这演示了如何使用扩展 trait 的类来实现单个 Scala trait。

Extending multiple traits

Scala 允许您创建具有特征的非常模块化的代码。 例如,您可以将动物的属性分解为小的、合乎逻辑的、模块化的单元:

trait Speaker {
    def speak(): String
}

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

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

一旦你有了这些小片段,你就可以通过扩展所有这些片段并实现必要的方法来创建一个 Dog 类:

class Dog extends Speaker with TailWagger with Runner {

    // Speaker
    def speak(): String = "Woof!"

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

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

}

请注意 extendswith 是如何从多个 trait 创建一个类的:

class Dog extends Speaker with TailWagger with Runner {
          -------  

这段代码的要点:

  • 使用 extends 来扩展第一个traits
  • 使用 with 来扩展后续的 trait

从你目前所见,Scala trait 就像 Java 接口一样工作。 但还有更多……

USING SCALA TRAITS LIKE ABSTRACT CLASSES

原文地址:https://docs.scala-lang.org/overviews/scala-book/traits-abstract-mixins.html

在上一章节中,我们展示了如何像原始 Java 接口一样使用 Scala 特征,但它们具有比这更多的功能。 您还可以向它们添加真正的工作方法,并将它们用作抽象类,或者更准确地说,用作 mixin。

A first example

为了证明这一点,这里有一个 Scala trait,它有一个名为 speak 的具体方法和一个名为 comeToMaster 的抽象方法:

trait Pet {
    def speak = println("Yo")     // concrete implementation of a speak method
    def comeToMaster(): Unit      // abstract
}

当一个类扩展一个 trait 时,必须实现每个抽象方法,所以这里有一个扩展 Pet 并定义了 comeToMaster 的类:

class Dog(name: String) extends Pet {
    def comeToMaster(): Unit = println("Woo-hoo, I'm coming!")
}

除非你想覆盖 speak,否则不需要重新定义它,所以这是一个完美的 Scala 类。 现在你可以像这样创建一个新的 Dog:

val d = new Dog("Zeus")

然后你可以调用speak和comeToMaster。 这是它在 REPL 中的样子:

scala> val d = new Dog("Zeus")
d: Dog = Dog@4136cb25

scala> d.speak
Yo

scala> d.comeToMaster
Woo-hoo, I'm coming!

Overriding an implemented method

一个类也可以覆盖一个在 trait 中定义的方法。 下面是一个例子:

class Cat extends Pet {
    // override 'speak'
    override def speak(): Unit = println("meow")
    def comeToMaster(): Unit = println("That's not gonna happen.")
}

REPL 展示了这是如何工作的:

scala> val c = new Cat
c: Cat = Cat@1953f27f

scala> c.speak
meow

scala> c.comeToMaster
That's not gonna happen.

Mixing in multiple traits that have behaviors

Scala trait 的一大优点是您可以将多个具有行为的 trait 混合到类中。 例如,这里有一个traits的组合,其中一个定义了一个抽象方法,另一个定义了具体的方法实现:

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")
}

现在你可以创建一个 Dog 类来扩展所有这些特征,同时为 speak 方法提供行为:

class Dog(name: String) extends Speaker with TailWagger with Runner {
    def speak(): String = "Woof!"
}

这是一个 Cat 类:

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

REPL 表明这一切都像您期望的那样工作。 先Dog:

scala> d.speak
res0: String = Woof!

scala> d.startRunning
I'm running

scala> d.startTail
tail is wagging

接着Cat

scala> val c = new Cat
c: Cat = Cat@1b252afa

scala> c.speak
res1: String = Meow

scala> c.startRunning
Yeah ... I don't run

scala> c.startTail
tail is wagging

Mixing traits in on the fly

最后要注意的是,您可以对具有具体方法的 trait 做的一件非常有趣的事情是将它们动态地混合到类中。 例如,鉴于这些特征:

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")
}

and this Dog class:

class Dog(name: String)

您可以在创建 Dog 实例时创建一个混合了这些特征的 Dog 实例:

val d = new Dog("Fido") with TailWagger with Runner
                        ---------------------------

REPL 再次表明这是有效的:

scala> val d = new Dog("Fido") with TailWagger with Runner 
d: Dog with TailWagger with Runner = $anon$1@50c8d274

scala> d.startTail
tail is wagging

scala> d.startRunning
I'm running

这个例子之所以有效,是因为 TailWaggerRunner 特征中的所有方法都已定义(它们不是抽象的)。

ABSTRACT CLASSES

原文地址:https://docs.scala-lang.org/overviews/scala-book/abstract-classes.html

Scala 也有一个抽象类的概念,类似于 Java 的抽象类。 但是因为traits 如此强大,你很少需要使用抽象类。 实际上,您只需要在以下情况下使用抽象类:

  • 您想创建一个需要构造函数参数的基类
  • 您的 Scala 代码将从 Java 代码中调用

Scala traits don’t allow constructor parameters

关于第一个原因,Scala trait 不允许构造函数参数

// this won’t compile
trait Animal(name: String)

因此,只要基本行为必须具有构造函数参数,就需要使用抽象类:

abstract class Animal(name: String)

但是,请注意一个类只能扩展一个抽象类。

When Scala code will be called from Java code

关于第二点——第二次需要使用抽象类时——因为 Java 对 Scala traits一无所知,如果你想从 Java 代码中调用你的 Scala 代码,你需要使用抽象类而不是trait。

Abstract class syntax

抽象类语法类似于特征语法。 例如,这里有一个名为Pet的抽象类,它类似于我们在上一课中定义的“Pet”特征:

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

鉴于抽象的Pet 类,您可以像这样定义一个Dog 类:

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

REPL 表明这一切都如宣传的那样:

scala> val d = new Dog("Rover")
d: Dog = Dog@51f1fe1c

scala> d.speak
Woof

scala> d.comeToMaster
Here I come!

Notice how name was passed along

所有这些代码都类似于 Java,所以我们不会详细解释它。 需要注意的一件事是 name 构造函数参数是如何从 Dog 类构造函数传递到 Pet 构造函数的:

class Dog(name: String) extends Pet(name) {

请记住,Pet 被声明为将 name 作为构造函数参数:

abstract class Pet (name: String) { ...

因此,此示例展示了如何将构造函数参数从 Dog 类传递到 Pet 抽象类。 您可以使用以下代码验证这是否有效:

abstract class Pet (name: String) {
    def speak: Unit = println(s"My name is $name")
}

class Dog(name: String) extends Pet(name)

val d = new Dog("Fido")
d.speak

我们鼓励您将该代码复制并粘贴到 REPL 中以确保它按预期工作,然后根据需要对其进行试验。

以上是关于Scala学习之TRAITS和抽象类的主要内容,如果未能解决你的问题,请参考以下文章

Scala的继承和抽象类

具有 Traits 的 Scala 客户端组合与实现抽象类

Scala入门学习之包类与对象

Scala 模式匹配详解

理解Scala的函数式编程思想

大数据Scala学习—列表 集与映射