一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时,也采用了extends关键字,如果有多个特质存在父类,那么需要采用with关键字连接
➢没有父类
class 类名 extends 特质1 with 特质2 with 特质3…
➢有父类
class 类名 extends 父类 with 特质1 with 特质2 with 特质3
trait的声明:
trait 特质名{
trait体
}
Scala中没有接口(interface)的概念, 类似于接口
简单例子来看一下trait的样貌:
Object Scala_Demo{
def main(args: Array[String]) : Unit = {
var user = new User()
println(user)
}
}
声明特质
trait TestTrait{
}
特质可以继承, 所以使用extends关键字
class User extends TestTrait{
}
输出:
zhang.shao.User@6eceb130
如果类继承多个特质,采用with连接
演示:
trait TestTrait{
}
trait TestTrait2{
}
class User extends TestTrait with TestTrait2{
}
如果一个类同时存在父类和特质, 依然采用继承方式,但是继承的是父类, 连接(混入)特质
class 类名 extends 父类 with 特质1 with 特质2 with 特质3
演示:
trait TestTrait{
}
trait TestTrait2{
}
class Person{
}
class User extends Person with TestTrait with TestTrait2{
}
java中的接口无法直接构建对象,必须使用实现类
scala中的特质也无法构建对象
scala中的特质是可以执行代码的. 比如把println打印语句放入特质中。
java接口中不可以放执行代码的.
演示:
package zhang.shao
object 演示区 {
def main(args: Array[String]) : Unit = {
val user = new User
}
}
trait TestTrait{
println("hello scala")
}
class User extends TestTrait{
}
输出:
hello scala
注:
- 一般执行的代码是放到方法里或者静态代码块或者代码块. 而Scala可以支持在特质里直接放入执行代码。 很方便!! 而java中在接口里是不能直接放入执行的代码…
- 查看反编译内容,可以看到特质里面有个初始化方法! 当创建类对象时,会首先执行特质内的内容.然后再执行构造方法里的内容!!
- 可以看到先执行了特质里的内容,然后再执行构造方法里的内容
java中的接口中可以声明方法的, 早期版本中声明的方法都是抽象,新版本的是可以有默认实现
java中的接口中可以声明属性的, 属性值无法修饰
scala特质中声明的属性和方法都可以在混入类(继承类,如下面代码中的User100类)中调用, 同时特质中声明的属性还可以修改.
演示:
package zhang.shao
object 演示区 {
def main(args: Array[String]): Unit = {
val user10 = new User100()
println(user10.name) // 打印了特质里的属性值
user10.name = "王五" // 修饰了特质里的属性
println(user10.name)
user10.test // 调用了特质里的方法
}
}
trait TestTrait100 {
// 声明属性
var name : String = "李四"
// 声明方法
def test = {
println("hello world")
}
}
class User100 extends TestTrait100 {
}
特质中可以声明抽象方法或者声明抽象属性.
演示:
package zhang.shao
object 演示区 {
def main(args: Array[String]): Unit = {
val user10 = new User100()
user10.test1
println(user10.username)
println(user10.age)
}
}
trait TestTrait100 {
声明抽象方法
def test1()
声明抽象属性
var username : String
声明一个变量
val age : Int = 20
}
class User100 extends TestTrait100 {
重写抽象方法
def test1: Unit = {
println("编程爱好者")
}
重写抽象属性
override var username: String = "iloveyou"
重写父类中的变量, 切记不能重写var声明的非抽象变量, 只能用val来重写非抽象属性
override val age : Int = 100
}
子类,父类, 以及特质的执行顺序
特质和父类没有关系, 只和当前的混入的类(如例子中的User100类)有关系,所以, 在调用时, 父类先执行, 然后当前混入的特质再执行, 然后当前类再执行
package zhang.shao
object 演示区 {
def main(args: Array[String]): Unit = {
val user10 = new User100()
}
}
// 特质(特征)
trait TestTrait100 {
println("tarit code....")
}
// 父类
class Person100{
println("parent code ......")
}
// 子类
class User100 extends Person100 with TestTrait100 {
println("child code ...")
}
输出:
parent code ......
tarit code....
child code ...
注:
1. 特质TestTrait100只和User100类有关系,和Person100类没有关系
2. Person100只和User100有关系. 与TestTrait100没有关系!!
3. 一个类有父类和特质, 父类先执行, 特质再执行,然后子类再执行
再看另一个例子:
如果父类和子类都混入了相同的特质,那么特质的代码只会执行一遍!
package zhang.shao
object 演示区 {
def main(args: Array[String]): Unit = {
val user10 = new User100()
}
}
trait TestTrait100 {
println("tarit code....")
}
class Person100 extends TestTrait100{
println("parent code ......")
}
class User100 extends Person100 with TestTrait100 {
println("child code ...")
}
输出:
tarit code....
parent code ......
child code ...
注:
1. 特质的内容只执行了一遍, 在Person100之前执行了一次, 到了User100的时候就没有执行特质内容.
2. 一个类有父类和特质, 父类先执行, 特质再执行,然后子类再执行
特质可以继承其他的特质, 特质还可以重写父特质的方法
演示:
object Scala_Demo {
def main(args: Array[String]): Unit = {
val user = new User()
user.insert()
}
}
父特质, 父特质中有个方法(不是抽象方法)
trait Operate{
def insert(){
println("插入数据")
}
}
子特质, 子特质可以重写父特质的方法. 用override关键字, 如果是抽象方法可以不用override, 如果是非抽象方法必须要用override
trait DB extends Operate{
override def insert(){
print(“向数据库”)
super.insert()
}
}
class MySql extends DB{
}
输出:
向数据库插入数据
假如一个类混入了多个特质, 会怎么样??
演示:
object Scala_Demo {
def main(args: Array[String]): Unit = {
val user = new User()
user.insert()
}
}
父特质, 父特质中有个方法(不是抽象方法)
trait Operate{
println("Operate....")
def insert(){
println("插入数据")
}
}
子特质, 子特质可以重写父特质的方法. 用override关键字, 如果是抽象方法可以不用override, 如果是非抽象方法必须要用override
trait DB extends Operate{
println("DB....")
override def insert(){
print(“向数据库”)
super.insert()
}
}
trait File extends Operate{
println("File......")
override def insert(){
print("向文件")
super.insert()
}
}
class MySql extends File with DB{
}
输出:
Operate......
File......
DB......
向数据库插入数据
注:
1. 多特质混入时, 可执行代码的执行顺序为从左到右, 如果有父特质, 会优先执行, 如果混入了两个特质(DB和File)有公共的父特质(Operate),那么父特质的代码只会执行一遍
2. 多特质混入时, 方法调用的顺序为从右到左!
3. 特质中的super关键字不是指代父特质, 指代的是上一级特质,从例子来看, DB特质上一级特质为File特质而不是Operate特质, File特质上一级特质没有了,super就会自动的指代父特质!
4. 如果希望super指向父特质, 需要增加特殊操作: super[特质]
如果希望super指向父特质, 需要增加特殊操作: super[特质]
此时就不会执行File特质里的insert方法内容!!
java的接口可以在scala中当成特质来使用
Trait动态混入
根据开放封闭原则,当需要在原来的代码中增加新的功能时,不允许修改原来的代码,即不能更改之前的类。为此,Scala引入了动态混入的语法,可以在创建对象时动态地添加新的Trait, 比如用extends关键字来, 就会造成了子类的功能的扩展, 既然是功能的扩展了, 就相当于在类的基础上混入了这个特质, 就改变了之前的代码, 也就对之前的代码进行了修改. 违背了OCP开发原则!!
演示:
package zhang.shao
object 演示区 {
def main(args: Array[String]): Unit = {
动态混入, 扩展性非常强!!
val user20 = new User200 with Operate5
user20.show
}
}
trait Operate5{
def show = {
println("展示数据")
}
}
class User200 {
}
特质继承类
自身类型(selftype)
有很多特质不是所有类都能随随便便继承的,例如异常类所能继承的特质,其他类继承并不合适,所以需要对特质加以约束。
关于自身类型可以看下面文章:
https://yanbin.blog/scala-self-type-understanding/#more-8528