1.不允许多重继承
所有的面向对象的语言都不允许直接的多重继承,因为会出现“deadly diamond of death”问题。Scala提供了特质(trait),特质可以同时拥有抽象方法和具体方法,一个类可以实现多个特质。
2.当做接口使用的特质
特质中没有实现的方法就是抽象方法。类通过extends继承特质,通过with可以继承多个特质。
trait Logger {
def log(msg: String)
}
class ConsoleLogger extends Logger with Cloneable with Serializable {
def log(msg: String) {
println(msg)
}
}
Logger with Cloneable with Serializable是一个整体,extends这个整体。所有的java接口都可以当做Scala特质使用。
3.带有具体实现的特质
特质中的方法并不一定是抽象的:
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("余额不足")
else balance -= amount
}
}
4.带有特质的对象,动态混入
在构建对象时混入某个具体的特质,覆盖掉抽象方法,提供具体实现:
trait Logger {
def log(msg: String)
}
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("余额不足")
else balance -= amount
}
}
object Main extends App {
val account = new SavingsAccount with ConsoleLogger
account.withdraw(100)
}
5.叠加在一起的特质
super并不是指继承关系,而是指的加载顺序。
继承多个相同父特质的类,会从右到左依次调用特质的方法。Super指的是继承特质左边的特质,从源码是无法判断super.method会执行哪里的方法,如果想要调用具体特质的方法,可以指定:super[ConsoleLogger].log(…).其中的泛型必须是该特质的直接超类类型
trait Logger {
def log(msg: String);
}
trait ConsoleLogger extends Logger {
def log(msg: String) {
println(msg)
}
}
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("余额不足")
else balance -= amount
}
}
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)
}
6.在特质中重写抽象方法
trait Logger2 {
def log(msg: String)
}
//因为有super,Scala认为log还是一个抽象方法
trait TimestampLogger2 extends Logger2 {
abstract override def log(msg: String) {
super.log(new java.util.Date() + " " + msg)
}
}
trait ShortLogger2 extends Logger2 {
abstract override def log(msg: String) {
super.log(if (msg.length <= 15) msg else s"${msg.substring(0, 12)}...")
}
}
trait ConsoleLogger2 extends Logger2 {
override def log(msg: String) {
println(msg)
}
}
class Account2 {
protected var balance = 0.0
}
abstract class SavingsAccount2 extends Account2 with Logger2 {
def withdraw(amount: Double) {
if (amount > balance) log("余额不足")
else balance -= amount
}
}
object Main2 extends App {
//这里可以根据12.5的知识点理解此处
val acct1 = new SavingsAccount2 with ConsoleLogger2 with TimestampLogger2 with ShortLogger2
acct1.withdraw(100)
}
7.当做富接口使用的特质
即该特质中既有抽象方法,又有非抽象方法
//富特质
trait Logger3 {
def log(msg: String)
def info(msg: String) {
log("INFO: " + msg)
}
def warn(msg: String) {
log("WARN: " + msg)
}
def severe(msg: String) {
log("SEVERE: " + msg)
}
}
trait ConsoleLogger3 extends Logger3 {
def log(msg: String) {
println(msg)
}
}
class Account3 {
protected var balance = 0.0
}
abstract class SavingsAccount3 extends Account3 with Logger3 {
def withdraw(amount: Double) {
if (amount > balance) severe("余额不足")
else balance -= amount
}
}
object Main3 extends App {
val acct = new SavingsAccount with ConsoleLogger
acct.withdraw(100)
}
8.特质中的具体字段
特质中可以定义具体字段,如果初始化了就是具体字段,如果不初始化就是抽象字段。
混入该特质的类就具有了该字段,字段不是继承,而是简单的加入类。是自己的字段。
trait Logger4 {
def log(msg: String)
}
trait ConsoleLogger4 extends Logger4 {
def log(msg: String) {
println(msg)
}
}
trait ShortLogger4 extends Logger4 {
val maxLength = 15
abstract override def log(msg: String) {
super.log(if (msg.length <= maxLength) msg else s"${msg.substring(0, maxLength - 3)}...")
}
}
class Account4 {
protected var balance = 0.0
}
class SavingsAccount4 extends Account4 with ConsoleLogger4 with ShortLogger4 {
var interest = 0.0
def withdraw(amount: Double) {
if (amount > balance) log("余额不足")
else balance -= amount
}
}
object Main4 extends App {
val acct = new SavingsAccount4
acct.withdraw(100)
println(acct.maxLength)
}
9.特质中的抽象字段
特质中未被初始化的字段在具体的子类中必须被重写。
//特质中的具体字段
trait Logger5 {
def log(msg: String)
}
trait ConsoleLogger5 extends Logger5 {
def log(msg: String) {
println(msg)
}
}
trait ShortLogger5 extends Logger5 {
val maxLength: Int
abstract override def log(msg: String) {
super.log(if (msg.length <= maxLength) msg else s"${msg.substring(0, maxLength - 3)}...")
}
}
class Account5 {
protected var balance = 0.0
}
abstract class SavingsAccount5 extends Account5 with Logger5 {
var interest = 0.0
def withdraw(amount: Double) {
if (amount > balance) log("余额不足")
else balance -= amount
}
}
object Main5 extends App {
val acct = new SavingsAccount5 with ConsoleLogger5 with ShortLogger5 {
val maxLength = 20
}
acct.withdraw(100)
println(acct.maxLength)
}
10.特质构造顺序
特质也是有构造器的,构造器中的内容由“字段的初始化”和一些其他语句构成
trait Logger6 {
println("我在Logger6特质构造器中,嘿嘿嘿。。。")
def log(msg: String)
}
trait ConsoleLogger6 extends Logger6 {
println("我在ConsoleLogger6特质构造器中,嘿嘿嘿。。。")
def log(msg: String) {
println(msg)
}
}
trait ShortLogger6 extends Logger6 {
val maxLength: Int
println("我在ShortLogger6特质构造器中,嘿嘿嘿。。。")
abstract override def log(msg: String) {
super.log(if (msg.length <= maxLength) msg else s"${msg.substring(0, maxLength - 3)}...")
}
}
class Account6 {
println("我在Account6构造器中,嘿嘿嘿。。。")
protected var balance = 0.0
}
abstract class SavingsAccount6 extends Account6 with ConsoleLogger6 with ShortLogger6{
println("我再SavingsAccount6构造器中")
var interest = 0.0
override val maxLength: Int = 20
def withdraw(amount: Double) {
if (amount > balance) log("余额不足")
else balance -= amount
}
}
object Main6 extends App {
val acct = new SavingsAccount6 with ConsoleLogger6 with ShortLogger6
acct.withdraw(100)
println(acct.maxLength)
}
步骤总结:
1、调用当前类的超类构造器
2、第一个特质的父特质构造器
3、第一个特质构造器
4、第二个特质构造器的父特质构造器由于已经执行完成,所以不再执行
5、第二个特质构造器
6、当前类构造器
11.初始化特质中的字段
特质不能有构造器参数,每个特质都有一个无参数的构造器。缺少构造器参数是特质与类之间唯一的技术差别。除此之外,特质可以具备类的所有特性,比如具体的和抽象的字段,以及超类。现在有如下情景:我们想通过特质来实现日志数据的输出,输出到某一个文件中
import java.io.PrintStream
trait Logger7{
def log(msg:String)
}
trait FileLogger7 extends Logger7{
val fileName:String
val out = new PrintStream(fileName)
override def log(msg: String): Unit = {
out.print(msg)
out.flush()
}
}
class SavingsAccount7{
}
object Main7 extends App {
val acct = new SavingsAccount7 with FileLogger7 {
override val fileName = "2017-11-24.log"//空指针异常
}
}
如果想修复如上错误,可以:
(1)使用“提前定义”
或这样提前定义:
package unit12
import java.io.PrintStream
trait Logger7 {
def log(msg: String)
}
trait FileLogger7 extends Logger7 {
val fileName: String
val out = new PrintStream(fileName)
override def log(msg: String): Unit = {
out.print(msg)
out.flush()
}
}
//提前定义在这里
class SavingsAccount7 extends {
override val fileName = "2017-11-24.log"
} with FileLogger7
object Main7 extends App {
val acct = new SavingsAccount7 with FileLogger7
acct.log("嘿嘿嘿")
}
(2)使用lazy
package unit12
import java.io.PrintStream
trait Logger7 {
def log(msg: String)
}
trait FileLogger7 extends Logger7 {
val fileName: String
lazy val out = new PrintStream(fileName)
override def log(msg: String): Unit = {
out.print(msg)
out.flush()
}
}
class SavingsAccount7 {
}
object Main7 extends App {
val acct = new SavingsAccount7 with FileLogger7 {
override val fileName = "2017-11-24.log"
}
acct.log("哈哈哈")
}
12.扩展类的特质
总结:
1、特质可以继承自类,以用来拓展该类的一些功能
2、所有混入该特质的类,会自动成为那个特质所继承的超类的子类
3、如果混入该特质的类,已经继承了另一个类,不就矛盾了?注意,只要继承的那个类是特质超类的子类即可。
例如:
(1)特质可以继承自类,以用来拓展该类的一些功能
trait LoggedException extends Exception{
def log(): Unit ={
println(getMessage())
}
}
(2)所有混入该特质的类,会自动成为那个特质所继承的超类的子类
class UnhappyException extends LoggedException{
override def getMessage = "哦,我的上帝,我要踢爆他的屁股!"
}
(3) 如果混入该特质的类,已经继承了另一个类,不就矛盾了?注意,只要继承的那个类是特质超类的子类即可。
正确:
class UnhappyException2 extends IndexOutOfBoundsException with LoggedException{
override def getMessage = "哦,我的上帝,我要踢爆他的屁股!"
}
错误:
class UnhappyException3 extends JFrame with LoggedException{
override def getMessage = "哦,我的上帝,我要踢爆他的屁股!"
}
13.自身类型
主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型。
比如:
//自身类型特质
trait Logger9{
this: Exception =>
def log(): Unit ={
println(getMessage)
}
}
这样一来,在该特质中,可以随意调用“自身类型”中的各种方法。
14.源码详见:https://github.com/luomingkui/scala