八、Scala从入门到精通一一面向对象编程(高级特性)

8.1、静态属性和静态方法

8.1.1、静态属性-提出问题

在这里插入图片描述

8.1.2、基本介绍

Scala中静态的概念-伴生对象

Scala语言是完全面向对象(万物皆对象)的语言,所以并没有静态的操作(即在Scala中没有静态的概念)。但是为了能够和Java语言交互(因为Java中有静态概念),就产生了一种特殊的对象来模拟类对象,我们称之为类的伴生对象。这个类的所有静态内容都可以放置在它的伴生对象中声明和调用

8.1.3、伴生对象的快速入门


object AccompanyObject {
  def main(args: Array[String]): Unit = {

    println(ScalaPerson.sex) // true 在底层等价于 ScalaPerson$.MODULE$.sex()
    ScalaPerson.sayHI() // 在底层等价于 ScalaPerson$.MODULE$.sayHI()
  }
}

// 说明:
// 1.当在同一个文件中,有class ScalaPerson 和 object ScalaPerson
// 2.class ScalaPerson 称为伴生类 ,将非静态的内容写到该类中
// 3.object ScalaPerson 称为伴生对象,将静态的内容写入到该对象(类)
// 4.class ScalaPerson 编译后底层生成 ScalaPerson类  ScalaPerson.class
// 5.object ScalaPerson编译后底层生成 ScalaPerson$类 ScalaPerson$.class
// 6.对于伴生对象的内容可以直接通过 ScalaPerson.属性 或者方法

// 伴生类
class ScalaPerson {
  var name: String = _
}

// 伴生对象
object ScalaPerson {
  var sex: Boolean = true

  def sayHI(): Unit = {
    println("object ScalaPerson 方法")
  }
}

对快速入门案列的源码剖析
在这里插入图片描述

8.1.4、伴生对象的小结

  1. Scala中伴生对象采用object关键字声明,伴生对象中声明的全是"静态"内容,可以通过伴生对象名称直接调用。
  2. 伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
  3. 伴生对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问
  4. 从语法角度来讲,所谓的伴生对象其实就是类的静态方法和成员的集合
  5. 从技术角度来讲,scala还是没有生成静态的内容,只不过是将伴生对象生成了一个新的类,实现属性和方法的调用
  6. 从底层原理看,伴生对象实现静态特性是依赖于publicstaticfinalMODULE$实现的。
  7. 伴生对象的声明应该和伴生类的声明在同一个源码文件中(如果不在同一个文件中会运行错误!),但是如果没有伴生类,也就没有所谓的伴生对象了,所以放在哪里就无所谓了。
  8. 如果classA独立存在,那么A就是一个类,如果objectA独立存在,那么A就是一个"静态"性质的对象[即类对象],在objectA中声明的属性和方法可以通过A.属性和A.方法来实现调用
  9. 当一个文件中,存在伴生类和伴生对象时,文件的图标会发生变化
    在这里插入图片描述

8.1.5、最佳实践-使用伴生对象完成小孩玩游戏

object ChildJoinGame {
  def main(args: Array[String]): Unit = {
    // 创建三个小孩
    val child0 = new Child02("白骨精")
    val child1 = new Child02("蜘蛛精")
    val child2 = new Child02("黄鼠狼精")
    Child02.JoinGame(child0)
    Child02.JoinGame(child1)
    Child02.JoinGame(child2)
    Child02.showNum()
  }
}

class Child02(cName:String) {
  var name = cName
}

object Child02 {
  // 用于统计共有多少小孩的属性
  var totalChildNum = 0

  def JoinGame(child: Child02): Unit ={
    printf("%s 小孩加入了游戏\n",child.name)
    // totalChildNum  + 1
    totalChildNum += 1
  }
  def showNum(): Unit ={
    printf("当前有%d小孩玩游戏\n",totalChildNum)
  }
}

8.1.6、伴生对象-apply方法

伴生对象中定义apply方法,可以实现:类名(参数)方式来创建对象实例.

object ApplyDemo {
  def main(args: Array[String]): Unit = {
    val list = List(1, 2, 3)
    println(list)

    val pig = new Pig("小花")

    // 使用apply方法来创建对象
    val pig2 = Pig("小黑猪") // 自动触发 (pName: String)
    val pig3 = Pig() // 自动触发 apply()
    println("pig2.name=" + pig2.name)
    println("pig3.name=" + pig3.name)
  }
}

// 案列演示 apply方法
class Pig(pName: String) {
  var name: String = pName
}

object Pig {
  // 编写一个apply方法
  def apply(pName: String): Pig = new Pig(pName)

  def apply(): Pig = new Pig("匿名猪猪")
}

8.1.7、练习题

在这里插入图片描述

object Exercise01 {
  def main(args: Array[String]): Unit = {
    println("========= Test1 =========")
    Frock.Initialization()
    println("Frock.getNextNum = " + Frock.getNextNum)
    println("Frock.getNextNum = " + Frock.getNextNum)
    println("========= Test2 =========")
    Frock.Initialization
    val Frock1 = new Frock()
    val Frock2 = new Frock()
    val Frock3 = new Frock()
    println("Frock1.serialNumber = " + Frock1.serialNumber)
    println("Frock2.serialNumber = " + Frock2.serialNumber)
    println("Frock3.serialNumber = " + Frock3.serialNumber)


  }
}

// 伴生类
class Frock() {
  val serialNumber = Frock.getNextNum()
}

// 伴生对象
object Frock {
  private var currentNum = 100000

  def Initialization() {
    println("Initialization...")
    currentNum = 100000
  }

  def getNextNum(): Int = {
    currentNum += 100
    currentNum
  }
}

8.2、接口

8.2.1、回顾Java接口

在这里插入图片描述

8.3.2、Scala接口的介绍

  1. 从面向对象来看,接口并不属于面向对象的范畴,Scala是纯面向对象的语言,在Scala中,没有接口。
  2. Scala语言中,采用特质trait(特征)来代替接口的概念,也就是说,多个类具有相同的特征(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明。理解trait等价于(interface+abstractclass)
  3. scala继承特质(trait)的示意图
    在这里插入图片描述

8.3.3、trait的说明

trait 特质名 {
trait体
}

  1. trait命名一般首字母大写

Cloneable,Serializable
object T1 extends Serializable{
}
Serializable:就是scala的一个特质

扫描二维码关注公众号,回复: 10560688 查看本文章

在scala中,java中的接口可以当做特质使用


object TraitDemo01 {
  def main(args: Array[String]): Unit = {


  }
}
//trait Serializable extends Any with java.io.Serializable
//在scala中,java的接口都可以当做trait来使用(如上面的语法)
object T1 extends Serializable {

}

object T2 extends Cloneable {

}


8.3.4、Scala中trait的使用

一个类具有某种特质(特征),就意味着这个类满足了这个特质(特征)的所有要素,所以在使用时也采用了extends关键字,如果有多个特质或存在父类,那么需要采用with关键字连接
在这里插入图片描述

8.4、特质(trait)

8.4.1、特质的快速入门案例

object TraitDemo02 {
  def main(args: Array[String]): Unit = {
    val c = new C()
    val f = new F()
    c.getConnet() //连接Mysql数据库
    c.getConnet() //连接Oracle数据库
  }
}

// 按照要求定义一个trait
trait Trait01 {
  // 定义一个规范
  def getConnet()
}

// 先将六个类的关系写出来
class A {}

class B extends A {}

class C extends A with Trait01 {
  override def getConnet(): Unit = {
    println("连接Mysql数据库")
  }
}

class D {}

class E extends D {}

class F extends D with Trait01 {
  override def getConnet(): Unit = {
    println("连接Oracle数据库")
  }
}

8.4.2、特质trait的再说明

1)Scala提供了特质(trait),特质可以同时拥有抽象方法和具体方法,一个类可以实现/继承多个特质

代码说明

object TraitDemo03 {
  def main(args: Array[String]): Unit = {
    println("·······")
    // 创建Sheep对象
    val sheep = new Sheep()
    sheep.sayHi()
    sheep.sayHello()
  }
}
// 当一个trait 有抽象方法和非抽象方法时
// 1. 一个trait在底层实现两个 Trait03.class 接口
// 2. 还对应 Trait03$class.class  Trait03$class 抽象类

trait Trait03 {
  // 抽象方法
  def sayHi()

  // 实现普通方法
  def sayHello(): Unit = {
    println("say Hello···")
  }
}

// 当 trait 有接口和抽象类时
// 1.class Sheep extends Trait03 在底层对应
// 2.class Sheep implements Trait03
// 3.当在 Sheep 类中要使用 Trait03的实现的方法,就通过 Trait03$class 调用
class Sheep extends Trait03 {
  override def sayHi(): Unit = {
    println("小羊 Say HI···")
  }
}

上面代码对应的底层的分析图
在这里插入图片描述
2) 特质中没有实现的方法就是抽象方法。类通过extends继承特质,通过with可以继承多个特质
3) 所有的java接口都可以当做Scala特质使用
在这里插入图片描述

8.4.3、带有特质的对象,动态混入

1)除了可以在类声明时继承特质以外,还可以在构建对象时混入特质,扩展目标类的功能
2)此种方式也可以应用于对抽象类功能进行扩展
3)动态混入是Scala特有的方式(java没有动态混入),可在不修改类声明/定义的情况下,扩展类的功能,非常的灵活,耦合性低。
4)动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能。
5)同时要注意动态混入时,如果抽象类有抽象方法,如何混入
6)案例演示

object MixInDemo01 {
  def main(args: Array[String]): Unit = {
    // 在不修改 类的定义基础,让他们可以使用trait方法
    val oracleDB = new OracleDB with Operate3
    oracleDB.insert(100)
    val mySQL = new MySQL3 with Operate3
    mySQL.insert(3)

    // 如果一个抽象类有抽象方法,如何动态混入特质
   val mysql_ =  new MySQL3_ with Operate3{
      override def say(): Unit = {
        println("say")
      }
    }
    mysql_.insert(1)
    mysql_.say()
  }
}

trait Operate3 { // 特质
  def insert(id: Int): Unit = { // 方法(实现)
    println("插入数据 = " + id)
  }
}

class OracleDB { // 空

}

abstract class MySQL3 { //空

}

abstract class MySQL3_ {
  def say()
}

课堂练习:在Scala中创建对象共有几种方式?
1)new对象
2)apply创建
3)匿名子类方式
4)动态混入

8.4.4、叠加特质

  • 基本介绍
    构建对象的同时如果混入多个特质,称之为叠加特质,那么特质声明顺序从左到右,方法执行顺序从右到左。
  • 叠加特质应用案例
    目的:分析叠加特质时,对象的构建顺序,和执行方法的顺序
    案例的代码
// 看看混入多个特质的特点(叠加特质)
object AddTraits {
  def main(args: Array[String]): Unit = {
    // 说明
    // 1.创建Mysql4的时候,动态混入了DB4 和 FIle4

    // 研究第一个问题,当我们创建一个动态混入对象时,其顺序是怎么样的
    // 总结一句话
    // Scala在叠加特质的时候,会首先从后面的特质开始执行(即从左到右)
    // 1 Operate4....
    // 2.Date4
    // 3.DB4
    // 4.File4

    val mysql = new MySQL4 with DB4 with File4
    println(mysql)

    //研究第二个问题,当我们执行一个动态混入对象的方法,其执行顺序是怎么样的
    // 顺序是,
    // (1).从右到左开始执行,
    // (2).当执行到super时,是指的左边的特质
    // (3),如果左边没有特质了,到super就是父特质
    // 1.向文件
    // 2.向数据库
    // 3.插入数据 100
    mysql.insert(100)

    println("======================")
    // 练习题
    val mysql1 = new MySQL4 with File4 with DB4
    println(mysql1)
    // 构建顺序
    // 1 Operate4....
    // 2.Date4
    // 3. FIle4
    // 4.DB4
println("*****************")
    // 执行顺序
    // 1.向数据库
    // 2.向文件
    // 3.插入数据 = 999
    mysql1.insert(999)
  }
}

trait Operate4 { // 特质
  println("Operate4...")

  def insert(id: Int) // 抽象方法
}

trait Date4 extends Operate4 { // 特质  继承了Operate4
  println("Date4")

  override def insert(id: Int): Unit = { // 重写/实现 Operate4的insert
    println("插入数据=" + id)
  }
}

trait DB4 extends Date4 { // 特质,继承了Date4
  println("DB4")

  override def insert(id: Int): Unit = { // 重写 Date4 的insert方法
    println("向数据库")
    super.insert(id)
  }
}

trait File4 extends Date4{ // 特质 继承 Date4
  println("File4")

  override def insert(id: Int): Unit = { // 重写 Date4 的 insert()
    println("向文件")
    //super.insert(id) // 调用了insert方法(难点) 这里super在动态混入时不一定是父类
    // 如果我们希望直接调用Data4的insert方法,可以指定,如下
    // 说明:super[?]?的类型,必须是当前的特质的直接父特质(超类)
    super[Date4].insert(id)
  }
}
class MySQL4{ // 普通类

}
  • 叠加特质注意事项和细节
    1)特质声明顺序从左到右。
    2)Scala在执行叠加对象的方法时,会首先从后面的特质(从右向左)开始执行
    3)Scala中特质中如果调用super,并不是表示调用父特质的方法,而是向前面(左边)继续查找特质,如果找不到,才会去父特质查找
    4)如果想要调用具体特质的方法,可以指定:super[特质].xxx(…).其中的泛型必须是该特质的直接超类类型
  • 叠加特质的练习
    要求:修改一下构建对象的混入多个特质的顺序,请说出输出结果
    在这里插入图片描述

8.4.5、当作富接口使用的特质

富接口:即该特质中既有抽象方法,又有非抽象方法

trait Operate{
definsert(id:Int)//抽象
def pageQuery(pageno:Int,pagesize:Int):Unit={//实现println("分页查询")
	}
}

8.4.6、特质中的具体字段

特质中可以定义具体字段,如果初始化了就是具体字段,如果不初始化就是抽象字段。混入该特质的类就具有了该字段,字段不是继承,而是直接加入类,成为自己的字段

object MixinPro {
  def main(args: Array[String]): Unit = {
    val mySQL6 = new MySQL6 with DB6 {
      override val sal = 0
    }
  }
}

trait DB6{
  val sal:Int
  var opertype:String = "insert"
  def insert(): Unit ={

  }
}
class MySQL6{}

反编译后的代码
在这里插入图片描述

8.4.7、特质中的抽象字段

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

8.4.8、特质构造顺序

  • 介绍
    特质也是有构造器的,构造器中的内容由“字段的初始化”和一些其他语句构成。具体实现请参考“特质叠加”
  • 第一种特质构造顺序(声明类的同时混入特质)
    1.调用当前类的超类构造器
    2.第一个特质的父特质构造器
    3.第一个特质构造器
    4.第二个特质构造器的父特质构造器,如果已经执行过,就不再执行
    5.第二个特质构造器
    6…重复4,5的步骤(如果有第3个,第4个特质)
    7.当前类构造器[案例演示]
  • 第二种特质构造顺序(在构建对象时,动态混入特质)
    1.调用当前类的超类构造器
    2.当前类构造器
    3.第一个特质构造器的父特质构造器
    4.第一个特质构造器.
    5.第二个特质构造器的父特质构造器,如果已经执行过,就不再执行
    6.第二个特质构造器
    7…重复5,6的步骤(如果有第3个,第4个特质)
    8.当前类构造器
  • 分析两种方式对构造顺序的影响
    第1种方式实际是构建类对象,在混入特质时,该对象还没有创建
    第2种方式实际是构造匿名子类,可以理解成在混入特质时,对象已经创建了
object MixinSeq {
  def main(args: Array[String]): Unit = {
    // 这时FF 是这样的 形式 class FF extends EE with CC with DD
    /*
       静态混入
      调用当前类的超类构造器
  第一个特质的父特质构造器
  第一个特质的构造器
  第二个特质的父特质构造器,如果已经执行过,就不在执行
  第一个特质的构造器
  ...........重复4,5,步骤(如果有第3个,第4个特质)
  当前类构造器
     */

    // 1.E...
    // 2.A...
    // 3.B...
    // 4.C...
    // 5.D...
    // 6.F...
    val ff1 = new FF()

    println(ff1)

    /*
    动态混入
    先创建new KK 对象,然后再混入

    调用当前类的超类构造器
 当前类构造器
 第一个特质构造器的父特质构造器
 第一个特质构造器
 第二个特质构造器的父特质构造器,如果已经执行过,就不再执行
 第二个特质构造器
 当前类的构造器
     */
    // 1.E...
    // 2.K....
    // 3.A....
    // 4.B....
    // 5.C....
    // 6.D....
    println("=================")
     val ff2 = new KK with CC with DD
     println(ff2)
  }
}

trait AA {
  println("A...")
}

trait BB extends AA {
  println("B...")
}

trait CC extends BB {
  println("C...")
}

trait DD extends BB {
  println("D...")
}

class EE { // 普通的类
  println("E...")
}

class FF extends EE with CC with DD { // 先继承了EE类,然后再继承 CC 和 DD
  println("F...")
}

class KK extends EE { // KK 直接继承了 普通的类EE
  println("K.....")
}

8.4.9、扩展类的特质

1.特质可以继承类,以用来拓展该特质的一些功能
2.所有混入该特质的类,会自动成为那个特质所继承的超类的子类

// 说明
// 1.LoggedException 继承了 Exception
// 2.LoggedException 特质就可以使用Exception 里面的功能
trait LoggedException extends Exception {
  def log(): Unit = {
    println(getMessage()) // 方法来自于EXception
  }
}

3.如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类,否则就会出现了多继承现象,发生错误


object ExtendTraitDemo01 {
  def main(args: Array[String]): Unit = {
    println(".....")
  }
}

// 说明
// 1.LoggedException 继承了 Exception
// 2.LoggedException 特质就可以使用Exception 里面的功能
trait LoggedException extends Exception {
  def log(): Unit = {
    println(getMessage()) // 方法来自于EXception
  }
}

// 因为 UnhappyException 继承了 LoggedException
// 而 LoggedException 继承了 Exception
// UnhappyException 就成了 Exception 子类
class UnhappyException extends LoggedException {
  // 已经是Exception 的子类了, 所以可以重写方法
  override def getMessage: String = "错误消息"
}


// 如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类
// 否则就会出现多继承现象,发生错误

class UnhappyException2 extends IndexOutOfBoundsException with LoggedException {
  // 已经是Exception 的子类了, 所以可以重写方法
  override def getMessage: String = "错误消息"
}

/*class CCC {}

// 错误的原因是 CCC 不是 Exception 子类
class UnhappyException3 extends CCC with LoggedException {
  // 已经是Exception 的子类了, 所以可以重写方法
  override def getMessage: String = "错误消息"
}*/

8.4.10、自身类型

  • 说明
    自身类型:主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型。

  • 应用案例举例说明
    自身类型特质,以及如何使用自身类型特质

object SelfTypeDemo {
  def main(args: Array[String]): Unit = {


  }
}

// Logger 就是自身类型特质,当这里做了自身类型后,那么
// trait Logger extends Exception, 要求混入该特质的对象的类必须也是 Exception的子类
trait Logger{
  // 明确告诉编译器,我就是Exception,如果没有这句话,下面的getMessage不能调用
  this:Exception =>
  def log(): Unit ={
    // 既然我就是Exception ,那么就可以调用其中的方法
    println(getMessage)
  }
}
// class Console extends Logger{} // 错误
class Console extends Exception with Logger // Ok

8.5、嵌套类(看源码,面试)

8.5.1、Scala嵌套类的使用

请编写程序,
定义Scala的成员内部类和静态内部类,并创建相应的对象实例。

代码如下
在这里插入图片描述

8.5.2、Scala嵌套类的使用2

请编写程序,
在内部类中访问外部类的属性。
方式1
内部类如果想要访问外部类的属性,可以通过外部类对象访问。

即:访问方式:外部类名.this.属性名代码:

 外部类
 内部类访问外部类的属性方法1 , 外部类名.this.属性
class ScalaOuterClass {
  // 定义两个属性
  var name = "scoot"
  private var sal = 40000.9

  class ScalaInnerClass { // 成员内部类
    def info() = {
      // 访问方式: 外部类名.this.属性名
      // 怎么理解 ScalaOuterClass.this 就相当于是 ScalaOuterClass 这个外部类的一个实例
      // 然后通过 ScalaOuterClass.this 实例对象去访问name属性
      // 只是这种写法比较特别,学习java的同学可能更容易理解 ScalaOuterClass.class的写法
      println("name = " + ScalaOuterClass.this.name
        + "age = " + ScalaOuterClass.this.sal)
    }
  }

}

方式2
内部类如果想要访问外部类的属性,也可以通过外部类别名访问(推荐)。
即:访问方式:外部类名别名.属性名
代码如下:

object ScalainnerClassDemo {
  def main(args: Array[String]): Unit = {
    // 测试,创建了两个外部类的实例
    val outer1: ScalaOuterClass = new ScalaOuterClass()
    val outer2: ScalaOuterClass = new ScalaOuterClass()


    // 在Scala中,创建成员内部类的语法是
    // 对象 . 内部类 的方式创建,
    // 这里语法可以看出在Scala中,默认情况下内部类实例和外部对象关联
    val inner1 = new outer1.ScalaInnerClass
    val inner2 = new outer2.ScalaInnerClass

    // 测试使用inner1 调用info
    inner1.info()

    // 这里调用test方法
    inner1.test(inner1)
    // 在默认情况下,scala的内部类的实例创建该内部类实例的外部对象关联
    inner2.test(inner1)
    inner2.test(inner2)

    // 创建静态内部类的实例
    val staticInner = new ScalaOuterClass.ScalaStaticInnerClass()
  }
}

// 外部类
// 内部类访问外部类的属性方法1 , 外部类名.this.属性
//class ScalaOuterClass {
//  // 定义两个属性
//  var name = "scoot"
//  private var sal = 40000.9
//
//  class ScalaInnerClass { // 成员内部类
//    def info() = {
//      // 访问方式: 外部类名.this.属性名
//      // 怎么理解 ScalaOuterClass.this 就相当于是 ScalaOuterClass 这个外部类的一个实例
//      // 然后通过 ScalaOuterClass.this 实例对象去访问name属性
//      // 只是这种写法比较特别,学习java的同学可能更容易理解 ScalaOuterClass.class的写法
//      println("name = " + ScalaOuterClass.this.name
//        + "age = " + ScalaOuterClass.this.sal)
//    }
//  }
//
//}

// 外部类
// 内部类访问外部类的属性方法2  使用别名的方式
// 1. 将外部类属性,写在别名后面
class ScalaOuterClass {
  myouter => // 这里我们可以理解 外部类的别名 看做是外部类的一个实例

  class ScalaInnerClass { // 成员内部类
    def info() = {
      // 访问方式: 外部类别名.属性名

      // 只是这种写法比较特别,学习java的同学可能更容易理解 ScalaOuterClass.class的写法
      println("name· = " + myouter.name
        + "age· = " + myouter.sal)
    }

    // 这里有一个方法,可以接收ScalaInnerClass实例
    // 下面的 ScalaOuterClass#ScalaInnerClass 类型投影的作用就是屏蔽
    // 外部对象对内部对象的影响
    def test(ic: ScalaOuterClass#ScalaInnerClass): Unit = {
      System.out.println("使用了类型投影" + ic)
    }
  }

  // 定义两个属性
  var name = "scoot"
  private var sal = 800.9
}


object ScalaOuterClass { // 伴生对象
class ScalaStaticInnerClass { // 静态内部类

}

}

解决方式-使用类型投影

类型投影是指:在方法声明上,如果使用外部类#内部类的方式,表示忽略内部类的对象关系,等同于Java中内部类的语法操作,我们将这种方式称之为类型投影(即:忽略对象的创建方式,只考虑类型)

发布了18 篇原创文章 · 获赞 25 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/weixin_44258756/article/details/105359126