1、特质
- Scala 语言中,采用特质 trait(特征)来代替接口的概念,也就是说,多个类具有相同的特质(特征)时,就可以将这个特质(特征)独立出来,采用关键字 trait 声明。
- Scala 中的 trait 中既可以有抽象属性和方法,也可以有具体的属性和方法,一个类可以混入(mixin)多个特质。这种感觉类似于 Java 中的抽象类。
- Scala 引入 trait 特征,第一可以替代 Java 的接口,第二个也是对单继承机制的一种补充。
1.1、特质的声明
基本语法
trait 特质名 { trait 主体 }
重写冲突属性案例
- 一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了 extends 关键字,如果有多个特质或存在父类,那么需要采用 with 关键字连接(相当于 Java 的 implements )。
- 如果一个类既继承了一个父类又继承了一个特质,那么如果这个父类和特质又相同的属性,在访问该属性时就会产生冲突,子类需要重写该属性。
class Person13{ val name: String = "person" val age: Int = 20 def sayHello(): Unit ={ println("hello " + name) } } //定义一个特质 trait Young { //声明一个非抽象属性 val name: String = "young" //声明一个抽象方法 def study(): Unit //声明一个非抽象方法 def play(): Unit = { println("play") } } class Student13 extends Person13 with Young { //重写冲突属性 override val name = "young student" //实现抽象方法 def study(): Unit = { println(s"student ${name} like study") } //重写父类方法 override def sayHello(): Unit = { super.sayHello() println(s"hello student ${name}") } }
1.2、动态混入
动态混入,也就是在创建对象的时候再去继承特质实现抽象属性和方法。
object Test { def main(args: Array[String]): Unit = { //动态混入 创建对象时灵活扩展特质 val student1 = new Student13 with sexTrait { //实现抽象属性 val 不可变更为 var val sex = "young student" } //调用混入的trait属性 student1.sex } } class Person13{ val name: String = "person" val age: Int = 20 def sayHello(): Unit ={ println("hello " + name) } } //声明一个特质 trait sexTrait { val sex: String } class Student13 extends Person13{ //重写父类方法 override def sayHello(): Unit = { super.sayHello() println(s"hello student ") } }
1.3、特质叠加
由于一个类可以混入(mixin )多个 trait ,且 trait 中可以有具体的属性和方法,若混入的特质中具有相同的方法(方法名,参数列表,返回值均相同),必然会出现继承冲突问题。冲突分为以下两种:
- 第一种,一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且两个 trait 之间没有任何关系,解决这类冲突问题,直接在类(Sub)中重写冲突方法。
trait A{ val num: Int def fun(): Unit = { println("A") } } trait B{ val num: Int = 10 def fun(): Unit = { println("B") } }
class Student14 extends Person13 with A with B{ //重写方法 fun override def fun(): Unit = { super.fun() //最后继承的特质是B 所以fun方法会调用B的fun方法 } } object Test14_TraitOverlying { def main(args: Array[String]): Unit = { val stu = new Student14 stu.fun() //输出 B println(stu.num) //10 } }
特质叠加的顺序
特质A和B中都含有变量num,但是特质A并没有初始化num的值,而特质B初始化了num的值。正因为最后继承了特质B,而特质B中实现了num变量的赋值,所以我们在子类中不需要实现或者重写num的值,可以直接访问num的值,这时候访问到的也就是特质B中初始化的值。
如果A也初始化了num的值,则B无法叠加。
如果A初始化了num的值,B没有,尽管最后继承的是特质B,但B仍然无法覆盖num,所以访问到的是A定义的num的值。
trait horse{ val name: String = "马" } trait donkey{ val name: String } class Animal{ } //骡子 class Mule extends Animal with horse with donkey { } object Test { def main(args: Array[String]): Unit = { val mule = new Mule println(mule.name) //马 } }
trait horse{ val name: String } trait donkey{ val name: String = "驴" } class Animal{ } //骡子 class Mule extends Animal with horse with donkey { } object Test { def main(args: Array[String]): Unit = { val mule = new Mule println(mule.name) //驴 } }
- 第二种,一个类(Sub)混入的两个 trait(TraitA,TraitB)中具有相同的具体方法,且两个 trait 继承自相同的 trait(TraitC),及所谓的“钻石问题”,解决这类冲突问题,Scala 采用了特质叠加的策略
object Test { def main(args: Array[String]): Unit = { val mule = new Mule println(mule.describe()) //骡子是 驴-马-爬行动物 } } trait horse extends crawler { val t1: String = "马-" override def describe(): String = t1 + super.describe() } trait donkey extends crawler { val t2: String = "驴-" override def describe(): String = t2 + super.describe() } //爬行动物特征 trait crawler { def describe(): String = { "爬行动物" } } //骡子 class Mule extends horse with donkey { override def describe(): String = "骡子是 " + super.describe() }
特质叠加顺序:骡子 -> 驴 -> 马 -> 爬行动物
指定父类的方法
直接 super[父类].方法名,这样就不会去使用叠加后的方法,而是直接使用我们指定的子类的某个父类的方法,不用担心被叠加的问题
class Mule extends horse with donkey { override def describe(): String = "骡子是 " + super[horse].describe() } object Test { def main(args: Array[String]): Unit = { val mule = new Mule println(mule.describe()) //骡子是 马-爬行动物 } }
super关键字
在Scala中,super关键字只能用于调用父类的方法,不能用于访问或调用父类的属性。这是因为在Scala中,属性访问器(getter和setter)实际上是方法的一种特殊形式。如果需要在子类中访问父类的属性,你可以在父类中定义一个公共的方法(getter方法)来返回属性的值。然后在子类中通过调用该方法来获取父类的属性值。
1.4、自身类型
自身类型实现了 依赖注入 的功能。
案例-用户注册
package chapter06 object Test16_SelfType { def main(args: Array[String]): Unit = { val user = new RegisterUser("tom","123456") user.register() } } class User(val name: String,val password: String) trait UserDao{ //把自身类型定义为User 相当于我们已经有一个User实例对象 直接在这操作 _: User => def register(): Unit = { val res = s""" |注册成功! |用户名: ${this.name} |密码: ${this.password} |""".stripMargin println(res) } } class RegisterUser(name: String,password: String) extends User(name, password) with UserDao
注册成功! 用户名: tom 密码: 123456
1.5、扩展
类型的检查与转换
- obj.isInstanceOf[T]:判断 obj 是不是 T 类型。
- obj.asInstanceOf[T]:将 obj 强转成 T 类型。
- classOf: 获取对象的类名。
枚举和应用类
枚举类:需要继承 Enumeration
应用类:需要继承 App
object Test { def main(args: Array[String]): Unit = { println(WorkDay.MONDAY) //Monday println(WorkDay.TUESDAY) //Tuesday } } //定义枚举类对象 object WorkDay extends Enumeration{ val MONDAY = Value(1,"Monday") val TUESDAY = Value(2,"Tuesday") } //定义应用类对象 不需要main方法 直接就可以运行 object TestApp extends App{ println("app start") }
Type
使用 type 关键字可以定义新的数据数据类型名称,本质上就是类型的一个别名
type Str = String val word: Str = "hello" print(word)