当前位置: 首页 > article >正文

5. scala高阶之traits

背景

上一节介绍了scala的隐式转换,本文主要介绍scala的另外一个强大功能traits。

1. Traits

在Scala中,traits是一种非常强大的特性,它允许你定义抽象类型成员、具体方法实现、以及可以在多个类之间共享的行为。

可以将Trait作为接口来使用,此时的Triat就与Java中的接口非常类似,在triat中可以定义抽象方法,就与抽象类中的抽象方法一样,只要不给出方法的具体实现即可,类可以使用extends关键字继承trait。

注意,这里不是implement,而是extends,在scala中没有implement的概念,无论继承类还是trait,统一都是extends,类继承trait后,必须实现其中的抽象方法,实现时不需要使用override关键字。Scala不支持对类进行多继承,但是支持多重继承trait,使用with关键字即可

1.1. 定义Traits

Traits通过trait关键字定义。它们可以包含抽象成员(没有实现的方法)和具体成员(有实现的方法)。

trait Greeter {
  def greet(): Unit
  def farewell(): Unit = println("Goodbye!") // 具体成员
}

1.2. 实现Traits

一个类可以通过extends关键字来实现一个或多个traits。如果一个trait包含抽象成员,那么实现该trait的类必须提供这些成员的具体实现。

class EnglishGreeter extends Greeter {
  def greet(): Unit = println("Hello!")
}

1.3. Traits的叠加(Stacking Traits)

一个类可以实现多个traits,从而组合多个行为。

trait Logger {
  def log(message: String): Unit = println(s"Log: $message")
}

class EnglishGreeterWithLogging extends EnglishGreeter with Logger {
  override def greet(): Unit = {
    log("Greeting someone")
    super.greet()
  }
}

1.4. Traits中的抽象类型成员

Traits可以包含抽象类型成员,这些成员在实现该trait的类中必须被具体化。

trait Container {
  type T
  def content: T
}

class IntContainer extends Container {
  type T = Int
  def content: T = 42
}

1.5. 多重继承

在Scala中,一个类可以继承多个trait,这种多重继承的机制带来了强大的灵活性。当一个类继承了多个trait,而这些trait中包含有相同名称的方法时,可以通过不同的方式来组织方法的调用顺序和行为。

以下是一些常见的实现方式:

1.5.1. 线性化(Linearization)

当一个类实现多个traits时,tScala 的 trait 继承机制采用线性化(linearization)规则来决定方法的调用顺序。这个顺序是由 Scala 编译器根据 trait 的定义顺序和继承关系自动计算的。简单来说,最后定义的 trait 的方法会覆盖前面定义的同名方法。

trait A {
  def sayHello(): Unit = println("Hello from A")
}

trait B {
  def sayHello(): Unit = println("Hello from B")
}

class C extends A with B {
  // 默认情况下,C 的 sayHello 方法会调用 B 的 sayHello,因为 B 在 A 之后被混合进 C
}

val c = new C()
c.sayHello()  // 输出: Hello from B

1.5.2. 显式调用超trait的方法

如果需要在某个 trait 中调用另一个 trait 的同名方法,可以使用 super 关键字,不过这种用法有些限制,因为它直接依赖于线性化顺序。Scala 提供了更灵活的方式,通过 self-type 注解或者辅助方法来实现。

1.5.2.1 使用 self-type 注解

有时,你可能需要在trait中引用实现该trait的类的类型。这可以通过自我类型注解来实现。

trait SelfTypeExample {
  this: SomeOtherTrait => // 这里指定了Self Type
  def doSomething(): Unit
}

trait SomeOtherTrait {
  def anotherMethod(): Unit
}

class ExampleClass extends SomeOtherTrait with SelfTypeExample {
  def anotherMethod(): Unit = println("Another method called")
  def doSomething(): Unit = println("Doing something")
}

使用方式如下:

trait A {
  def sayHello(): Unit = println("Hello from A")
}

trait B {
  this: A =>  // 指定 B 必须和 A 一起被混合
  override def sayHello(): Unit = {
    super.sayHello()  // 调用 A 的 sayHello
    println("Hello from B")
  }
}

class C extends A with B

val c = new C()
c.sayHello()  // 输出: Hello from A
              //       Hello from B
1.5.2.2 使用辅助方法

如果 self-type 注解不适合,可以使用辅助方法来显式调用其他 trait 的方法。

trait A {
  def sayHelloA(): Unit = println("Hello from A")
  def sayHello(): Unit = sayHelloA()
}

trait B {
  def sayHelloA(): Unit  // 声明辅助方法
  override def sayHello(): Unit = {
    sayHelloA()  // 调用 A 的 sayHelloA
    println("Hello from B")
  }
}

class C extends A with B {
  override def sayHelloA(): Unit = super[A].sayHelloA()  // 显式调用 A 的 sayHelloA
}

val c = new C()
c.sayHello()  // 输出: Hello from A
              //       Hello from B

1.5.3. 使用默认参数和方法重载

有时可以通过参数化方法或重载方法来实现更复杂的行为,不过这更多依赖于具体的应用场景。

trait A {
  def sayHello(prefix: String = ""): Unit = println(s"$prefixHello from A")
}

trait B {
  override def sayHello(prefix: String = ""): Unit = {
    super.sayHello("B's ")
    println(s"$prefixHello from B")
  }
}

class C extends A with B

val c = new C()
c.sayHello()  // 输出: B's Hello from A
              //       Hello from B

通过以上方式,Scala 提供了丰富的机制来处理和调用多个 trait 中的同名方法,允许开发者根据需要灵活地组织方法调用顺序和行为。

1.6. Traits的早期定义(Early Definitions)

在Scala中,你可以使用早期定义来在构造器的初始化列表中引用traits中的方法。

trait EarlyDefTrait {
  def earlyMethod(): Unit
}

class EarlyDefClass extends {
  val x = earlyMethod() // 在这里调用earlyMethod
} with EarlyDefTrait {
  def earlyMethod(): Unit = println("Early method called")
}

总结

Scala的traits提供了一种灵活且强大的代码复用机制,使得你可以定义可组合的行为和抽象类型成员。通过traits,你可以创建出高度模块化和可维护的代码库。

2. 下划线在scala中的作用

  1. 在初始化变量的时候给定默认值
  2. 将函数赋值给变量的时候,参数占位,表示赋值的是函数本身
    ,而不是函数执行结果
  3. 在变长参数函数调用过程中,传入集合元素的时候告诉编译器传递的是集合中的元素
  4. 高阶函数调用过程中,参数占位
  5. 引入包的时候,表示包下的所有类和子包
  6. 模式匹配中对于不需要的数据可以使用_代替

以上,如有错误,请不吝指正!


http://www.kler.cn/a/533976.html

相关文章:

  • YK人工智能(六)——万字长文学会基于Torch模型网络可视化
  • RTMP 和 WebRTC
  • 100.6 AI量化面试题:如何评估AI量化模型的过拟合风险?
  • llama.cpp GGUF 模型格式
  • 【PyQt】使用PyQt5和Matplotlib实现的CSV数据可视化工具
  • 小程序越来越智能化,作为设计师要如何进行创新设计
  • C# 程序计算圆的面积(Program to find area of a circle)
  • Git、Github和Gitee完整讲解:丛基础到进阶功能
  • 鸿蒙Harmony-Refresh 容器组件
  • 【kafka的零拷贝原理】
  • [Java]函数式编程
  • 基于微信小程序的居住证申报系统设计与实现(LW+源码+讲解)
  • SpringBoot扩展篇:@Scope和@Lazy源码解析
  • Scala语言的人工智能
  • 搭建集成开发环境PyCharm
  • vue2-为啥data属性是一个函数而不是对象
  • 基于SpringBoot的在线远程考试系统的设计与实现(源码+SQL脚本+LW+部署讲解等)
  • [创业之路-276]:从燃油汽车到智能汽车:工业革命下的价值变迁
  • Nginx高并发性能优化
  • vue2-key的原理与作用
  • 开源安全一站式构建!开启企业开源治理新篇章
  • Java 中 LinkedList 的底层源码
  • 【后端开发】系统设计101——通信协议,数据库与缓存,架构模式,微服务架构,支付系统(36张图详解)
  • 在C#中使用DeepSeek API实现自然语言处理、文本分类、情感分析等
  • HTML语言的软件工程
  • flutter 专题四十七 Flutter 应用启动流程分析