简介

Scala语言中,采用特质trait(特征)来代替接口的概念,也就是说,多个类具有相同的特质(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明

Scala中的trait中即可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质。这种感觉类似于Java中的抽象类

Scala引入trait特征,第一可以替代Java的接口,第二个也是对单继承机制的一种补充

特质声明

  1. 基本语法 ```scala trait 特质名 { trait主体 }
  1. 2. **案例实操**
  2. ps:通过查看字节码,可以看到 特质 = 抽象类 + 接口
  3. ```scala
  4. trait PersonTrait {
  5. // 声明属性
  6. var name: String = _
  7. // 声明方法
  8. def eat(): Unit = {
  9. }
  10. // 抽象属性
  11. var age: Int
  12. // 抽象方法
  13. def say(): Unit
  14. }

特质基本语法

一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了extends关键字,如果有多个特质或存在父类,么需要采用with关键字连接

  1. 基本语法 ```scala 没有父类: class 类名 extends 特质1 with 特质2 with 特质3 … 有父类: class 类名 extends 父类 with 特质1 with 特质2 with 特质3…
  1. 2. **说明**
  2. 1)类和特质的关系:使用继承的关系。<br />(2)当一个类去继承特质时,第一个连接词是extends,后面是with。<br />(3)如果一个类在同时继承特质和父类时,应当把父类写在extends
  3. 3. **案例实操**
  4. 1)特质可以同时拥有抽象方法和具体方法
  5. ```scala
  6. trait PersonTrait {
  7. //声明属性
  8. var name: String = _
  9. //抽象属性
  10. var age: Int
  11. //声明方法
  12. def eat(): Unit = {
  13. println("eat")
  14. }
  15. //抽象方法
  16. def say(): Unit
  17. }

(2)一个类可以混入(mixin)多个特质
(3)所有的Java接口都可以当做Scala特质使用

  1. class Teacher extends PersonTrait with java.io.Serializable {
  2. override def say(): Unit = {
  3. println("say")
  4. }
  5. override var age: Int = _
  6. }

(4)动态混入:可灵活的扩展类的功能,创建对象时混入trait,而无需使类混入该trait,如果混入的trait中有未实现的方法,则需要实现

  1. object TestTrait {
  2. def main(args: Array[String]): Unit = {
  3. val teacher = new Teacher
  4. teacher.say()
  5. teacher.eat()
  6. val t2 = new Teacher with SexTrait {
  7. override var sex: String = "男"
  8. }
  9. //调用混入trait的属性
  10. println(t2.sex)
  11. }
  12. }

没有冲突的叠加

如果没有接口叠加顺序的话, 就是从右到左

  1. object HelloWorld {
  2. def main(args: Array[String]): Unit = {
  3. val student1 = new Student13()
  4. student1.increase()
  5. }
  6. }
  7. trait Knowledge15 {
  8. var amount: Int = 0
  9. def increase(): Unit = {
  10. println("knowledge increased")
  11. }
  12. }
  13. trait Talent15 {
  14. def increase(): Unit = {
  15. println("talent increased")
  16. }
  17. }
  18. // 定义一个父类
  19. class Person13 {
  20. val name: String = "person"
  21. var age: Int = 18
  22. def sayHello(): Unit = {
  23. println("hello from: " + name)
  24. }
  25. def increase(): Unit = {
  26. println("person increase")
  27. }
  28. }
  29. class Student13 extends Person13 with Talent15 with Knowledge15 {
  30. // 重写冲突的属性
  31. override val name: String = "student"
  32. override def increase(): Unit = {
  33. super.increase()
  34. }
  35. }

特质叠加

1.说明
由于一个类可以混入(mixin)多个trait,且trait中可以有具体的属性和方法,若混入的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。冲突分为以下两种:

(1)一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法
image.png
(2)一个类(Sub)混入的两个trait(TraitA,TraitB)中具有相同的具体方法,且两个trait继承自相同的trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala采用了 特质叠加 的策略
image.png

  1. 案例实操

所谓的特质叠加,就是将混入的多个trait中的冲突方法叠加起来

  1. trait Ball {
  2. def describe(): String = {
  3. "ball"
  4. }
  5. }
  6. trait Color extends Ball {
  7. override def describe(): String = {
  8. "blue-" + super.describe()
  9. }
  10. }
  11. trait Category extends Ball {
  12. override def describe(): String = {
  13. "foot-" + super.describe()
  14. }
  15. }
  16. class MyBall extends Category with Color {
  17. override def describe(): String = {
  18. "my ball is a " + super.describe()
  19. }
  20. }
  21. object TestTrait {
  22. def main(args: Array[String]): Unit = {
  23. println(new MyBall().describe())
  24. }
  25. }
  1. 输出结果:
  2. my ball is a blue-foot-ball

特质叠加执行顺序

  1. 思考

上述案例中的super.describe()调用的是父trait中的方法吗?

当一个类混入多个特质的时候,scala会对所有的特质及其父特质按照一定的顺序进行排序,而此案例中的super.describe()调用的实际上是排好序后的下一个特质中的 describe() 方法,排序规则如下:
image.png
发现category继承ball, color也继承ball, 继承的一样, 先看extend是是谁? 谁就先列出来, 然后后面的那个接到上面前面
就是 color->category->ball

  1. 结论

(1)案例中的super,不是表示其父特质对象,而是表示上述叠加顺序中的下一个特质,即,MyClass中的super指代Color,Color中的super指代Category,Category中的super指代Ball
(2)如果想要调用某个指定的混入特质中的方法,可以增加约束:super[],例如 super[Category].describe()

特质自身类型

  1. 说明

自身类型可实现依赖注入的功能。 直接把这个类型的注入进来了

  1. 案例实操 ```scala class User(val name: String, val age: Int)

trait Dao { def insert(user: User) = { println(“insert into database :” + user.name) } }

trait APP { // 可以 abc: Dao => 也行 _: Dao =>

def login(user: User): Unit = { println(“login :” + user.name) insert(user) } }

object MyApp extends APP with Dao { def main(args: Array[String]): Unit = { login(new User(“bobo”, 11)) } }

  1. ```scala
  2. class User(val name: String, val password: String)
  3. trait UserDao {
  4. _: User =>
  5. // 向数据库插入数据
  6. def insert(): Unit = {
  7. // 可以用自身类型的属性, 但是要this.
  8. println(s"insert into db: ${this.name}")
  9. }
  10. }

特质的泛型

特征作为泛型类型和抽象方法非常有用

  1. trait Iterator[A] {
  2. def hasNext: Boolean
  3. def next(): A
  4. }

扩展 trait Iterator [A] 需要一个类型 A 和实现方法hasNext和next。
使用 extends 关键字来扩展特征。然后使用 override 关键字来实现trait里面的任何抽象成员:

  1. trait Iterator[A] {
  2. def hasNext: Boolean
  3. def next(): A
  4. }
  5. class IntIterator(to: Int) extends Iterator[Int] {
  6. private var current = 0
  7. override def hasNext: Boolean = current < to
  8. override def next(): Int = {
  9. if (hasNext) {
  10. val t = current
  11. current += 1
  12. t
  13. } else 0
  14. }
  15. }
  16. val iterator = new IntIterator(10)
  17. iterator.next() // returns 0
  18. iterator.next() // returns 1

和java一样,所有需要用到Traits的地方都可以用他的子类型代替。

  1. import scala.collection.mutable.ArrayBuffer
  2. trait Pet {
  3. val name: String
  4. }
  5. class Cat(val name: String) extends Pet
  6. class Dog(val name: String) extends Pet
  7. val dog = new Dog("Harry")
  8. val cat = new Cat("Sally")
  9. val animals = ArrayBuffer.empty[Pet]
  10. animals.append(dog)
  11. animals.append(cat)
  12. animals.foreach(pet => println(pet.name)) // Prints Harry Sally

这里animals需要的是Pet类型,我们可以用dog和cat代替

特质和抽象类的区别

(1)优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类
(2)如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行(有无参构造)