本文部分参考自:https://blog.csdn.net/dataiyangu/article/details/98483033
自己的话:漆黑的黑夜 表示着威胁 我选择诙谐
工作在堆叠 没兑现归结于那些理解
Scala 和 Java 都不允许多重继承,多继承会出现菱形继承问题,Scala 提 供了特质,类似于 Java 中的接口,特质可以同时拥有抽象方法和具体方法, 一个类可以实现多个特质。
如图一个父类被两个子类继承,然后两个子类都重写了父类的方法,这个时候又有一个子类同时继承了上面中间的两个子类,这个时候就会出现问题,相当于最上面的父类在类中有两个,这可能不是我们想要的结果,增加调用的困难,同时也会浪费内存资源。
(1)Scala中的Triat可以不是只定义抽象方法,还可以定义具体方法,此时trait更像是包含了通用工具方法的东西
(2)有一个专有的名词来形容这种情况,就是说trait的功能混入了类
举例来说,trait中可以包含一些很多类都通用的功能方法,比如打印日志等等,spark中就使用了trait来定义了通用的日志打印方法
trait Logger {
def log(message: String) = println(message)
}
class Person(val name: String) extends Logger {
def makeFriends(p: Person) {
println("Hi, I'm " + name + ", I'm glad to make friends with you, " + p.name)
//log调用继承接口的方法
log("makeFriends methdo is invoked with parameter Person[name=" + p.name + "]")
}
}
trait Logger {
def log(msg: String) // An abstract method
}
class ConsoleLogger extends Logger // Use extends, not implements
with Cloneable with Serializable { // Use with to add multiple traits def
log(msg: String) { println(msg) } // No override needed
}
特质中没有实现的方法就是抽象方法。
类通过 extends 继承特质,通过 with 可以继承多个特质。
Logger with Cloneable with Serializable 是一个整体,extends 这个整体 所有的 java 接口都可以当做 Scala 特质使用。
** 带有具体实现的特质:**
特质中的方法并不一定是抽象的,也可以有具体实现比如
trait
ConsoleLogger {
def log(msg: String) { println(msg) }
}
class Account {
protected var balance = 0.0
}
class SavingsAccount extends Account with ConsoleLogger { def withdraw(amount: Double) {
if (amount > balance) log("Insufficient funds")
else balance -= amount
}
// More methods ...
}
使 SavingsAccount 得到了一个具体的 log 方法(minix)混入, 并不是通过继承得来。
(1)Scala中的Triat可以定义具体field,此时继承trait的类就自动获得了trait中定义的field
(2)但是这种获取field的方式与继承class是不同的:如果是继承class获取的field,实际是定义在父类中的;而继承trait获取的field,就直接被添加到了类中
trait Person {
val eyeNum: Int = 2
}
class Student(val name: String) extends Person {
def sayHello = println("Hi, I'm " + name + ", I have " + eyeNum + " eyes.")
}
特质中可以定义具体字段,如果初始化了就是具体字段,如果不初始化 就是抽象字段。
混入该特质的类就具有了该字段,字段不是继承,而是简单的加入类。 是自己的字段
trait
Logger {
def log(msg: String)
}
trait ConsoleLogger extends Logger { def log(msg: String) { println(msg) }
}
trait ShortLogger extends Logger { val maxLength = 15
abstract override def log(msg: String) { super.log(
if (msg.length <= maxLength) msg
else s"${msg.substring(0, maxLength - 3)}...") }
}
class Account {
protected var balance = 0.0
}
// 只要有一个具体方法即可
class SavingsAccount extends Account with ConsoleLogger with ShortLogger { var interest = 0.0
def withdraw(amount: Double) {
if (amount > balance) log("Insufficient funds")
else balance -= amount
}
// More methods ...
}
object Main extends App {
val acct = new SavingsAccount acct.withdraw(100) acct.maxLength
}
上面的shortloger里面定义了一个字段maxLength=15,savingAccount里面继承了这个shortloger,所以可以将这个maxLength拿过来直接用
(1)Scala中的Triat可以定义抽象field,而trait中的具体方法则可以基于抽象field来编写
(2)但是继承trait的类,则必须覆盖抽象field,提供具体的值
trait SayHello {
val msg: String
def sayHello(name: String) = println(msg + ", " + name)
}
class Person(val name: String) extends SayHello {
val msg: String = "hello"
def makeFriends(p: Person) {
sayHello(p.name)
println("I'm " + name + ", I want to make friends with you!")
}
}
特质中未被初始化的字段在具体的子类中必须被重写
trait
Logger {
def log(msg: String)
}
trait ConsoleLogger extends Logger {
def log(msg: String) { println(msg) }
}
trait ShortLogger extends Logger {
val maxLength: Int // An abstract field abstract override def log(msg: String) {
super.log(
if (msg.length <= maxLength) msg
else s"${msg.substring(0, maxLength - 3)}...")
} }
class Account {
protected var balance = 0.0
}
abstract class SavingsAccount extends Account with Logger { var interest = 0.0
def withdraw(amount: Double) {
if (amount > balance) log("Insufficient funds")
else balance -= amount
}
// More methods ...
}
object Main extends App {
val acct = new SavingsAccount with ConsoleLogger with ShortLogger {
val maxLength = 20
}
acct.withdraw(100)
// Log message is not truncated because maxLength is 20
}
如果删掉子类的初始化的话会提示编译错误
到目前为止特质有点像抽象类,但是他能够叠加
为实例混入trait
有时我们可以在创建类的对象时,指定该对象混入某个trait,这样,就只有这个对象混入该trait的方法,而类的其他对象则没有
trait Logged {
def log(msg: String) {}
}
trait MyLogger extends Logged {
override def log(msg: String) { println("log: " + msg) }
}
class Person(val name: String) extends Logged {
def sayHello { println("Hi, I'm " + name); log("sayHello is invoked!") }
}
val p1 = new Person("leo")
p1.sayHello
val p2 = new Person("jack") with MyLogger
p2.sayHello
trait Logger{
def log(msg: String);
}
// 继承 Logger
trait ConsoleLogger extends Logger { def log(msg: String) { println(msg) }
}
class Account {
protected var balance = 0.0
}
abstract class SavingsAccount extends Account with Logger { def withdraw(amount: Double) {
if (amount > balance) log("Insufficient funds")
else balance -= amount
}
// More methods ...
}
object Main extends App {
val acct = new SavingsAccount with ConsoleLogger
acct.withdraw(100)
}
在构建对象时混入某个具体的特质,覆盖掉抽象方法,提供具体实现。
这里注意特质是能够继承特质的。
上面的newperson是新建了一个抽象子类,这个时候继承了特质ConsoleLogger,这个ConsoleLogger里面实现了log方法,所以会替换掉person原本继承的logger中没有的log方法,所以最后输出了ConsoleLogger中的log方法
(1)Scala中支持让类继承多个trait后,依次调用多个trait中的同一个方法,只要让多个trait的同一个方法中,在最后都执行super.方法即可
(2)类中调用多个trait中都有的这个方法时,首先会从最右边的trait的方法开始执行,然后依次往左执行,形成一个调用链条
(3)这种特性非常强大,其实就相当于设计模式中的责任链模式的一种具体实现依赖
trait Handler {
(4)这不是抽象方法,去掉{}才是抽象方法
def handle(data: String) {}
}
trait DataValidHandler extends Handler {
override def handle(data: String) {
println("check data: " + data)
super.handle(data)
}
}
trait SignatureValidHandler extends Handler {
override def handle(data: String) {
println("check signature: " + data)
super.handle(data)
}
}
class Person(val name: String) extends SignatureValidHandler with DataValidHandler {
def sayHello = { println("Hello, " + name); handle(name) }
}
叠加在一起的特质
trait Logger {
def log(msg: String);
}
// 继承 Logger 特质,提供具体的 log 方法
trait ConsoleLogger extends Logger {
def log(msg: String) { println(msg) }
}
// 注意 super
trait TimestampLogger extends ConsoleLogger {
override def log(msg: String) {
super.log(new java.util.Date() + " " + msg)
}
}
trait ShortLogger extends ConsoleLogger {
override def log(msg: String) {
super.log(
if (msg.length <= 15) msg else s"${msg.substring(0, 12)}...")
}
}
class Account {
protected var balance = 0.0
}
abstract class SavingsAccount extends Account with Logger {
def withdraw(amount: Double) {
if (amount > balance) log("Insufficient funds")
else balance -= amount
}
// More methods ...
}
object Main extends App {
val acct1 = new SavingsAccount with TimestampLogger with ShortLogger
val acct2 = new SavingsAccount with ShortLogger with TimestampLogger acct1.withdraw(100)
acct2.withdraw(100)
}
new SavingsAccount with TimestampLogger with ShortLogger,上面可以看到SavingsAccount这个抽象类继承了logger这个特质,那么SavingsAccount这个抽象子类就可以with多个logger的子特质,然后这写子特质的调用顺序是从右往左调用的
super 并不是指继承关系,而是指的加载顺序。
ShortLogger的supper就是TimestampLogger,因为TimestampLogger定义在ShortLogger前面,TimestampLogger前面没有supper了,那么TimestampLogger的supper就是默认的ConsoleLogger。
所以能够看到最先是截取了后面的十二个字符Insufficient,然后包装到TimestampLogger,前面加上时间,最后调用ConsoleLogger的log,打印出来
继承多个相同父特质的类,会从右到左依次调用特质的方法。Super 指的 是继承特质左边的特质,从源码是无法判断 super.method 会执行哪里的方 法,如果想要调用具体特质的方法,可以指定: super[ConsoleLogger].log(…).
1、在trait中,是可以覆盖父trait的抽象方法的
2、但是覆盖时,如果使用了super.方法的代码,则无法通过编译。因为super.方法就会去掉用父trait的抽象方法,此时子trait的该方法还是会被认为是抽象的
3、此时如果要通过编译,就得给子trait的方法加上abstract override修饰
trait Logger {
def log(msg: String)
}
trait MyLogger extends Logger {
abstract override def log(msg: String) { super.log(msg) }
}
1、在trait中,可以混合使用具体方法和抽象方法
2、可以让具体方法依赖于抽象方法,而抽象方法则放到继承trait的类中去实现
3、这种trait其实就是设计模式中的模板设计模式的体现
trait Valid {
def getName: String
def valid: Boolean = {
getName == "leo"
}
}
class Person(val name: String) extends Valid {
println(valid)
def getName = name
}
(1)在Scala中,trait也是有构造代码的,也就是trait中的,不包含在任何方法中的代码
(2)而继承了trait的类的构造机制如下:
class Person { println("Person's constructor!") }
trait Logger { println("Logger's constructor!") }
trait MyLogger extends Logger { println("MyLogger's constructor!") }
trait TimeLogger extends Logger { println("TimeLogger's constructor!") }
class Student extends Person with MyLogger with TimeLogger {
println("Student's constructor!")
}
特质也可以有构造器,由字段的初始化和其他特质体重的语句构成
只不过这个构造器是不能够设置参数的
那这个构造器的一些方法体是在哪里写呢?
就是在整个特质的定义过程中
trait FileLogger extends Logger {
println("Constructing FileLogger")
val out = new PrintWriter("app.log") // Part of the trait’s constructor
out.println(s"# ${java.time.Instant.now()}") // Also part of the constructor
def log(msg: String) { out.println(msg); out.flush() }
}
在写语句在任何混入该特质的对象在构造时都会被执行。
因为特质也是需要被实例化的
在Scala中,trait是没有接收参数的构造函数的,这是trait与class的唯一区别,但是如果需求就是要trait能够对field进行初始化,该怎么办呢?只能使用Scala中非常特殊的一种高级特性——提前定义
trait SayHello_13_10_1 {
val msg: String
println(msg.toString)
}
class Person_13_10_1
val p = new {
val msg: String = "init"
} with Person_13_10_1 with SayHello_13_10_1
class Person_13_10_2 extends {
val msg: String = "init"
} with SayHello_13_10_1 {}
// 另外一种方式就是使用lazy value
trait SayHello_13_10_2 {
lazy val msg: String = null
println(msg.toString)
}
class Person_13_10_4 extends SayHelloSayHello_13_10_2 {
override lazy val msg: String = "init"
}
初始化特质中的字段
特质不能有构造器参数,每个特质都有一个无参数的构造器。缺少构造 器参数是特质与类之间唯一的技术差别。 除此之外,特质可以具备类的所有 特性,比如具体的和抽象的字段,以及超类。
上面会编译不通过
通过提前定义来实现对特质的字段定义。
通过上面的构造方法的顺序知道fileLogger的构造方法是在savingsAccount之前调的,所以这里就会出现filename还没有初始化呢,下面就已经通过printStrean实例化好了。
在Scala中,trait也可以继承自class,此时这个class就会成为所有继承该trait的类的父类
class MyUtil {
def printMessage(msg: String) = println(msg)
}
trait Logger extends MyUtil {
def log(msg: String) = printMessage("log: " + msg)
}
class Person(val name: String) extends Logger {
def sayHello {
log("Hi, I'm " + name)
printMessage("Hi, I'm " + name)
}
}
扩展类的特质
特质和特质的继承层次比较常见,特质也可以扩展类,这个类将成为混
入该特质的超类
这里的ioException是上面的exception的子类,所以能够继承这个loggedException这个特质
如果我们的类扩展自一个不想关的类,那么就不可能混入这个特质了。