11.Scala特质Triat

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_15014327/article/details/83689462

一.Trait介绍

  1. 在Java语言中,只允许继承一个超类,该类可以实现多个接口。
  2. 但Java接口有其自身的局限性:接口中只能包括抽象方法,不能包含字段、具体方法。
  3. 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的区别:

  1. 无论是继承abstract class或者混入trait,对于一个要实现的类来说,必须先用extends,剩下的用with,否则编译的时候就会出错。当同时实现abstract class和trait的时候,abstract class必须在前,而trait必须在后,如果仅实现其一的话,只可以用extends
  2. 当直接new一个实现了trait对象的时候是可以使用with关键字的
  3. T with U可以是一个新的type,但是T extends U并不是一个新的类型

二.trait构造顺序

构造器是按以下顺序执行的: 

  1. 如果有超类,则先调用超类的构造器 
  2. 如果有父trait,它会按照继承层次先调用父trait的构造器 
  3. 如果有多个父trait,则按顺序从左到右执行 
  4. 所有父类构造器和父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
}

参考自:https://blog.csdn.net/column/details/scalalearning.html

猜你喜欢

转载自blog.csdn.net/qq_15014327/article/details/83689462