版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_15014327/article/details/83689462
一.Trait介绍
- 在Java语言中,只允许继承一个超类,该类可以实现多个接口。
- 但Java接口有其自身的局限性:接口中只能包括抽象方法,不能包含字段、具体方法。
- Scala语言利用Trait解决了该问题,在Scala的Trait中,它不但可以包括抽象方法还可以包含字段和具体方法。
示例:
trait Test{}
trait Dao{
//定义抽象方法不需要加abstract,加了反而报错
def delete(id:String):Boolean
def add(o:Any):Boolean
def update(o:Any):Int
def query(id:String):List[Any]
//可以带非抽象方法
def print():Unit = {
println("this is dao!")
}
//可以带抽象字段
var dbId:Int
}
//继承trait
class MysqlDao extends Dao{
def add(o: Any): Boolean = true
def delete(id: String): Boolean = true
def query(id: String): List[Any] = List.apply(1,2,3)
def update(o: Any): Int = 1
//必须初始化
var dbId:Int = 1
}
//继承多个trait
class OracleDao extends Dao with Cloneable with Test{
def add(o: Any): Boolean = true
def delete(id: String): Boolean = true
def query(id: String): List[Any] = List.apply(1,2,3)
def update(o: Any): Int = 1
//必须初始化
var dbId:Int = 2
}
总结一下extends和with的区别:
- 无论是继承abstract class或者混入trait,对于一个要实现的类来说,必须先用extends,剩下的用with,否则编译的时候就会出错。当同时实现abstract class和trait的时候,abstract class必须在前,而trait必须在后,如果仅实现其一的话,只可以用extends
- 当直接new一个实现了trait对象的时候是可以使用with关键字的
- T with U可以是一个新的type,但是T extends U并不是一个新的类型
二.trait构造顺序
构造器是按以下顺序执行的:
- 如果有超类,则先调用超类的构造器
- 如果有父trait,它会按照继承层次先调用父trait的构造器
- 如果有多个父trait,则按顺序从左到右执行
- 所有父类构造器和父trait被构造完之后,才会构造本类
trait FileLogger extends Logger
class Person
class Student extends Person with FileLogger with Cloneable
上述构造器的执行顺序为:
1 首先调用父类Person的构造器
2 调用父trait Logger的构造器
3 再调用trait FileLogger构造器,再然后调用Cloneable的构造器
4 最后才调用Student的构造器
三.类与trait比较
//1.不能定义trait带参数的构造器
//trait ConsoleLogger(msg:String){}
//2.trait与普通的scala类并没有其它区别
//3.trait中可以没有抽象的方法
trait ConsoleLogger extends Logger{
println("ConsoleLogger ...")
val console = new PrintWriter(System.out)
console.println("#")
def log(msg:String):Unit={
console.print(msg)
console.flush()
}
}
四.提前定义与懒加载
我们先看一个示例:
object Test {
def main(args: Array[String]): Unit = {
//不能写成new FileLogger(),因为它是trait
new FileLogger{}.log("I have a dream.")
}
}
trait Logger {
println("Logger constructor ...")
def log(msg: String): Unit
}
trait FileLogger extends Logger {
println("FileLogger constructor ...")
val fileName:String = "file.log"
val fileOutput = new PrintWriter(fileName)
fileOutput.println("#")
def log(msg: String): Unit = {
fileOutput.print(msg)
fileOutput.flush()
}
}
但是,程序中我们在FileLogger中写死了"file.log",这样不灵活。现在,我们想让FileLogger变得灵活有两种方式:提前定义和懒加载。
1.提前定义:虽然trait不能有带参数的构造方法,但是它的实现子类(class)可以有。
object Test {
def main(args: Array[String]): Unit = {
new Student("file.log").log("I have a dream!")
}
}
trait Logger {
println("Logger constructor ...")
def log(msg: String): Unit
}
trait FileLogger extends Logger {
println("FileLogger constructor ...")
val fileName:String
val fileOutput = new PrintWriter(fileName)
fileOutput.println("#")
def log(msg: String): Unit = {
fileOutput.print(msg)
fileOutput.flush()
}
}
class Person{
println("Person constructor ...")
}
class Student(file:String) extends Person with FileLogger{
println("Student constructor ...")
//Student类对FileLogger中的抽象字段重写
//...提前定义...
val fileName:String = file
}
虽然编译可以通过,但是运行报错:
Person constructor ...
Logger constructor ...
Exception in thread "main" FileLogger constructor ...
java.lang.NullPointerException
看日志我们发现构造顺序执行到Logger是正常的,到了FileLogger开始报错了。因为执行到FileLogger的时候,val fileName:String还没有被初始化(直到Student类中才被初始化),所以报错"空指针异常"。(构造器执行顺序的问题)
2.懒加载
import java.io.PrintWriter
object Test {
def main(args: Array[String]): Unit = {
new Student("file.log").log("I have a dream!")
}
}
trait Logger {
println("Logger constructor ...")
def log(msg: String): Unit
}
trait FileLogger extends Logger {
println("FileLogger constructor ...")
val fileName:String
//懒加载
lazy val fileOutput = new PrintWriter(fileName)
//这句话也不能出现,因为它是在构造方法中执行的,懒加载之后构造FileLogger对象时还没有实例化fileOutput。
//fileOutput.println("#")
def log(msg: String): Unit = {
fileOutput.print(msg)
fileOutput.flush()
}
}
class Person{
println("Person constructor ...")
}
class Student(file:String) extends Person with FileLogger{
println("Student constructor ...")
//Student类对FileLogger中的抽象字段重写
//...提前定义...
val fileName:String = file
}
执行结果如下:
Person constructor ...
Logger constructor ...
FileLogger constructor ...
Student constructor ...
五.self type
class Hello{
//self相当于this的别名,它不是关键字。不一定必须叫self
//self =>
my =>
var x = 1
def foo = this.x + my.x
}