Scala第七天——面向对象编程——Trait特质


本文部分参考自:https://blog.csdn.net/dataiyangu/article/details/98483033


Scala第七天——面向对象编程

自己的话:漆黑的黑夜 表示着威胁 我选择诙谐

工作在堆叠 没兑现归结于那些理解

Scala特质


一、不允许多重继承

Scala 和 Java 都不允许多重继承,多继承会出现菱形继承问题,Scala 提 供了特质,类似于 Java 中的接口,特质可以同时拥有抽象方法和具体方法, 一个类可以实现多个特质。

如图一个父类被两个子类继承,然后两个子类都重写了父类的方法,这个时候又有一个子类同时继承了上面中间的两个子类,这个时候就会出现问题,相当于最上面的父类在类中有两个,这可能不是我们想要的结果,增加调用的困难,同时也会浪费内存资源。


二、将trait作为接口使用

1.简述:

(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 + "]")
  }
}

2.详解:

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 特质使用。
Scala第七天——面向对象编程——Trait特质_第1张图片


三、在Trait中定义具体方法

** 带有具体实现的特质:**
特质中的方法并不一定是抽象的,也可以有具体实现比如

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)混入, 并不是通过继承得来。
Scala第七天——面向对象编程——Trait特质_第2张图片


四、在Trait中定义具体字段

1.简述:

(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.")
}

2.详解:

特质中可以定义具体字段,如果初始化了就是具体字段,如果不初始化 就是抽象字段。

混入该特质的类就具有了该字段,字段不是继承,而是简单的加入类。 是自己的字段

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
}

Scala第七天——面向对象编程——Trait特质_第3张图片
上面的shortloger里面定义了一个字段maxLength=15,savingAccount里面继承了这个shortloger,所以可以将这个maxLength拿过来直接用


五、在Trait中定义抽象字段

1.简述:

(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!")
  }
}

2.详解:

特质中未被初始化的字段在具体的子类中必须被重写

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
}

如果删掉子类的初始化的话会提示编译错误
Scala第七天——面向对象编程——Trait特质_第4张图片
到目前为止特质有点像抽象类,但是他能够叠加


六、带有特质的对象

为实例混入trait

1.简述:

有时我们可以在创建类的对象时,指定该对象混入某个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

2.详解:

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)
}

在构建对象时混入某个具体的特质,覆盖掉抽象方法,提供具体实现。
Scala第七天——面向对象编程——Trait特质_第5张图片
Scala第七天——面向对象编程——Trait特质_第6张图片

这里注意特质是能够继承特质的。
上面的newperson是新建了一个抽象子类,这个时候继承了特质ConsoleLogger,这个ConsoleLogger里面实现了log方法,所以会替换掉person原本继承的logger中没有的log方法,所以最后输出了ConsoleLogger中的log方法


七、trait调用链

1.简述:

(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) }
}

2.详解:

叠加在一起的特质

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(…).


八、在trait中覆盖抽象方法

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) }
}


九、混合使用trait的具体方法和抽象方法

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
}

十、trait的构造机制

1.简述:

(1)在Scala中,trait也是有构造代码的,也就是trait中的,不包含在任何方法中的代码

(2)而继承了trait的类的构造机制如下:

  • 父类的构造函数执行;
  • trait的构造代码执行,多个trait从左到右依次执行;
  • 构造trait时会先构造父trait,如果多个trait继承同一个父trait,则父trait只会构造一次;
  • 所有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!")
}

2.详解:

特质也可以有构造器,由字段的初始化和其他特质体重的语句构成
只不过这个构造器是不能够设置参数的
那这个构造器的一些方法体是在哪里写呢?
就是在整个特质的定义过程中

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特质_第7张图片



十一、 trait field的初始化

1.简述:

在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"
}

2.详解:

初始化特质中的字段

特质不能有构造器参数,每个特质都有一个无参数的构造器。缺少构造 器参数是特质与类之间唯一的技术差别。 除此之外,特质可以具备类的所有 特性,比如具体的和抽象的字段,以及超类。
Scala第七天——面向对象编程——Trait特质_第8张图片
上面会编译不通过
通过提前定义来实现对特质的字段定义。

通过上面的构造方法的顺序知道fileLogger的构造方法是在savingsAccount之前调的,所以这里就会出现filename还没有初始化呢,下面就已经通过printStrean实例化好了。
Scala第七天——面向对象编程——Trait特质_第9张图片


十二、trait继承class

1.简述:

在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)
  }
}

2.详解:

扩展类的特质

特质和特质的继承层次比较常见,特质也可以扩展类,这个类将成为混
入该特质的超类

Scala第七天——面向对象编程——Trait特质_第10张图片
这里的ioException是上面的exception的子类,所以能够继承这个loggedException这个特质
如果我们的类扩展自一个不想关的类,那么就不可能混入这个特质了。



让优秀成为习惯


你可能感兴趣的:(Scala基础)