8. Scala面向对象编程(高级部分)

8.1 静态属性和静态方法

  8.1.1 静态属性-提出问题 

      有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?请使用面向对象的思想,编写程序解决

      小孩堆雪人

  8.1.2 基本介绍

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

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

  8.1.3 伴生对象的快速入门 

object boke_demo01 {

  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 sayHI~~")
  }
}

      -对快速入门的案例的源码分析

      源码分析

  8.1.4 伴生对象的小结 

      1) Scala中伴生对象采用object关键字声明,伴生对象中声明的全是“静态”内容,可以通过伴生对象名称直接调用

      2) 伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致

      3) 伴生对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问

      4) 从语法角度来讲,所谓的伴生对象其实就是类的静态方法和成员的集合

      5) 从技术角度来讲,Scala还是没有生成静态的内容,只不过是将伴生对象生成了一个新的类,实现属性和方法调用[反编译看源码]

      6) 从底层原理看,伴生对象实现静态特性是依赖 public static final MOUDLE$ 实现的

      7) 伴生对象的声明应该和伴生类的声明在同一个源码文件中(如果不在同一个文件中会运行错误),但是如果没有伴生类,也就没有所谓的伴生对象了,所以放在哪里就无所谓了

      8) 如果 class A 独立存在,那么A就是一个类,如果 Object A 独立存在,那么A就是一个“静态”性质的对象[即类对象],在 Object A 中声明的属性和方法可以通过 A.属性和A.方法 来实现调用

      9) 当一个文件中,存在半生类和伴生对象时,文件的图标会发生变化

  8.1.5 最佳实践-使用伴生对象完成小孩堆雪人游戏 

      设计一个var total Int 表示总人数,我们在创建一个小孩时,就把total加1,并且total是所有对象共享的就ok了,使用伴生对象来解决

object boke_demo01 {

  def main(args: Array[String]): Unit = {
    //创建三个小孩
    val child0 = new Child("铁蛋")
    val child1 = new Child("狗蛋")
    val child2 = new Child("熊大")
    Child.joinGame(child0)
    Child.joinGame(child1)
    Child.joinGame(child2)
    Child.showNum()
  }
}

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

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

  def joinGame(child: Child): Unit = {
    printf("%s 小孩加入了游戏\n", child.name)
    //totalChildNum 加1
    totalChildNum += 1
  }

  def showNum(): Unit = {
    printf("当前有%d小孩玩游戏\n", totalChildNum)
  }
}

  8.1.6 伴生对象-apply方法 

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

object boke_demo01 {

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

    val pig = new Pig("狗蛋")

    //使用apply方法来创建对象
    val pig2 = Pig("铁蛋") //自动  apply(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.2 单例对象 

      这个部分将在Scala设计模式专题进行介绍

8.3 接口 

  8.3.1 回顾Java接口 

      -声明接口

        interface接口名

      -实现接口

        class 类名 implements 接口1,接口2

      -Java接口的使用小结

        1) 在Java中,一个类可以实现多个接口

        2) 在Java中,接口之间支持多继承

        3) 接口中属性都是常量

        4) 接口中的方法都试抽象的

  8.3.2 Scala接口的介绍 

      1) 从面向对象来看,接口并不属于面向对象的范畴,Scala是纯面向对象的语言,在Scala中,没有接口

      2) Scala语言中,采用特质trait(特征)来代替接口的概念,也就是说,多个类具有相同的特质(特征)时,就可以将这个特质(特征)独立出来,采用关键字trait声明。理解trait等价于(interface+abstract class)

      3) Scala继承特质(trait)的示意图

      Scala继承特质示意图

  8.3.3 trait的声明 

      trait 特质名 {

        trait 体

      }

      1) trait 命名 一般首字母大写 Cloneable,Serializable

        object T1 extends Serializable {

        }

        Serializable:就是Scala的一个特质

      -在Scala中,Java中的接口可以当做特质使用

object boke_demo01 {

  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关键字连接

      1) 没有父类

      class 类名 extends 特质1 with 特质2 with 特质3...

      2) 有父类

      class 类名 extends 父类 with 特质1 with 特质2 with 特质3...

8.4 特质(trait)

  8.4.1 特质的快速入门案例

      Scala引入trait特质,第一可以替代Java的接口,第二也是对单继承机制的一种补充

      引入特质

  8.4.2 案例代码

object boke_demo01 {

  def main(args: Array[String]): Unit = {
    val c = new C()
    val f = new F()
    c.getConnect() // 连接mysql数据库...
    f.getConnect() // 连接oracle数据库..
  }
}

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

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

class B extends A {}

class C extends A with Trait {
  override def getConnect(): Unit = {
    println("连接mysql数据库...")
  }
}

class D {}

class E extends D {}

class F extends D with Trait {
  override def getConnect(): Unit = {
    println("连接oracle数据库..")
  }
}

  8.4.3 特质trait的再说明

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

object boke_demo01 {

  def main(args: Array[String]): Unit = {
    //创建sheep
    val sheep = new Sheep
    sheep.sayHi()
    sheep.sayHello()
  }
}

//当一个trait有抽象方法和非抽象方法时
//1. 一个trait在底层对应两个 Trait.class 接口
//2. 还对应 Trait$class.class Trait$class抽象类
trait Trait {
  //抽象方法
  def sayHi()

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


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

特质的再说明

      2) 特质中没有实现的方法就是抽象方法。类通过extends继承特质,通过with关键字可以继承多个特质

      3) 所有的Java接口都可以当做Scala特质使用

      Java接口当做Scala的特质

 

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

      1) 除了可以在类声明时继承特质以外,还可以在构建对象时混入特质,扩展目标类的功能

      2) 此种方法也可以应用于对抽象类功能进行扩展

      3) 动态混入是Scala特有的方式(Java没有动态混入),可在不修改类声明/定义的情况下,扩展类的功能,非常的灵活,耦合性低

      4) 动态混入可以在不影响原有的继承关系的基础上,给指定的类扩展功能

      5) 同时要注意动态混入时,如果抽象类有抽象方法,如何混入

      6) 案例演示

object boke_demo01 {

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

    val mySQL = new MySQL with Operate
    mySQL.insert(200)

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

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

class OracleDB { //空
}

abstract class MySQL { //空
}

abstract class MySQL_ { //空
  def say()
}

      -在Scala中创建对象的4种方式

      1) new 对象

      2) apply 创建

      3) 匿名子类方式

      4) 动态混入  

  8.4.5 叠加特质 

      -基本介绍

      构建对象的同时如果混入多个特质,称之为叠加特质,那么特质声明顺序从左到右,方法执行顺序从右到左

      -叠加特质应用案例

      目的:分析叠加特质时,对象的构建顺序,和执行方法的顺序

      案例演示:

object boke_demo01 {

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

    //说明
    //1. 创建 MySQL实例时,动态的混入 DB 和 File

    //研究第一个问题,当我们创建一个动态混入对象时,其顺序是怎样的
    //总结一句话
    //Scala在叠加特质的时候,会首先从后面的特质开始执行(即从左到右)
    //1.Operate...
    //2.Data
    //3.DB
    //4.File
    val mysql = new MySQL with DB with File
    println(mysql)

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

    println("===================================================")
    //练习题
    val mySQL = new MySQL with File with DB
    mySQL.insert(999)
    //构建顺序
    //1.Operate...
    //2.Data
    //3.File
    //4.DB

    //执行顺序
    //1. 向数据库
    //2. 向文件
    //3. 插入数据 = 999
  }
}

trait Operate { //特点
  println("Operate...")

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

trait Data extends Operate { //特质,继承了Operate
  println("Data")

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

trait DB extends Data { //特质,继承 Data
  println("DB")

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

trait File extends Data { //特质,继承 Data
  println("File")

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

class MySQL {} //普通类

      -叠加特质注意事项和细节

        1) 特质声明顺序从左到右

        2) Scala 在执行叠加对象的方法时,会首先从后面的特质(从右向左)开始执行

        3) Scala 中特质中如果调用 super,并不是表示调用父特质的方法,而是向前面(左边)继续 查找特质,如果找不到,才会去父特质查找

        4) 如果想要调用具体特质的方法,可以指定:super[特质].xxx(...).其中的泛型必须是该特质的直接超类类型

  8.4.6 当作富接口使用的特质 

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

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

  8.4.7 特质中的具体字段 

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

object boke_demo01 {

  def main(args: Array[String]): Unit = {
    val mySQL = new MySQL with DB {
      override var sal = 10
    }
  }
}

trait DB {
  var sal: Int //抽象字段
  var opertype: String = "insert"

  def insert(): Unit = {
  }
}

class MySQL {}

      -反编译后的代码

编译后的代码

  8.4.8 特质中的抽象字段  

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

  8.4.9 特质构造顺序 

      -介绍

        特质也是有构造器的,构造器中的内容由“字段的初始化”和一些其他语句构成

      -第一种特质构造顺序(声明类的同时混入特质)

        1) 调用当前类的超类构造器

        2) 第一个特质的父特质构造器

        3) 第一个特质构造器

        4) 第二个特质构造器的父特质构造器,如果已经执行过就不再执行

        5) 第二个特质构造器

        6) ......重复4,5的步骤(如果有第3个,第4个特质)

        7) 当前类构造器

      -第二种特质构造顺序(在构建对象时,动态混入特质)

        1) 调用当前类的超类构造器

        2) 当前类构造器

        3) 第一个特质构造器的父特质构造器

        4) 第一个特质构造器

        5) 第二个特质构造器的父特质构造器,如果已经执行过就不再执行

        6) 第二个特质构造器

        7) ......重复4,5的步骤(如果有第3个,第4个特质)

        8) 当前类构造器

      -两种方式对构造顺序的影响

        1) 第一种方式实际是构建类对象,在混入特质时,该对象还没有创建

        2) 第二种方式实际是构造匿名子类,可以理解成在混入特质时,对象已经创建了

      -案例演示

object boke_demo01 {

  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 对象,然后再混入其它特质

    调用当前类的超类构造器
当前类构造器
第一个特质构造器的父特质构造器
第一个特质构造器.
第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行
第二个特质构造器
.......重复5,6的步骤(如果有第3个,第4个特质)
当前类构造器   [案例演示]

     */
    //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.10 扩展类的特质 

      -特质可以继承类,以用来拓展该特质的一些功能

trait LoggedException extends Exception {
  def log(): Unit = {
    println(getMessage()) // 方法来自于Exception类
  }
}

      -所有混入该特质的类,会自动成为那个特质所继承的超类的子类

//1. LoggedException 继承了 Exception
//2. LoggedException 特质就可以  Exception 功能
trait LoggedException extends Exception {
  def log(): Unit = {
    println(getMessage()) // 方法来自于Exception类
  }
}

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

object boke_demo01 {

  def main(args: Array[String]): Unit = {
    println("h~~")
  }
}

//说明
//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 = "错误消息!"
}

// 如果混入该特质的类,已经继承了另一个类(A类),则要求A类是特质超类的子类,
// 否则就会出现了多继承现象,发生错误。
class UnhappyException2 extends IndexOutOfBoundsException with LoggedException {
  // 已经是Exception的子类了,所以可以重写方法
  override def getMessage = "错误消息!"
}

class CCC {}

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

  8.4.11 自身类型 

      -说明

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

      -应用案例

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

object boke_demo01 {

  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 {}//对吗? 正确

8.5 嵌套类

  8.5.1 嵌套类的使用1 

  

  8.5.2 Scala嵌套类的使用2

      编写程序,在内部类中访问外部类的属性

      -方式1

        内部类如果想要访问外部类的属性,可以通过外部类对象访问

        即访问形式:外部类名.this.属性名

        案例演示

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

  class ScalaInnerClass { //成员内部类,

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

}

      -方式2

        内部类如果想要访问外部类的属性,也可以通过外部类别名访问(推荐)

        即访问方式:外部类名别名.属性名

        案例演示

object boke_demo01 {

  def main(args: Array[String]): Unit = {
    //测试1. 创建了两个外部类的实例
    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的内部类的实例和创建该内部类实例的外部对象关联.
    //
    inner1.test(inner2)
    inner2.test(inner2)


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


  }
}


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

    def info() = {
      // 访问方式:外部类别名.属性名
      // 只是这种写法比较特别,学习java的同学可能更容易理解 ScalaOuterClass.class 的写法.
      println("name~ = " + myouter.name
        + " sal~ =" + myouter.sal)
    }
  }

  //定义两个属性
  var name = "Jack"
  private var sal = 999.9
}


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

}

  8.5.3 类型投影  

      -案例演示

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

    def info() = {
      // 访问方式:外部类别名.属性名
      // 只是这种写法比较特别,学习java的同学可能更容易理解 ScalaOuterClass.class 的写法.
      println("name~ = " + myouter.name
        + " sal~ =" + myouter.sal)
    }

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

  }

  //定义两个属性
  var name = "Jack"
  private var sal = 999.9
}

      -解决方式-类型投影 

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

 

posted on 2019-05-05 21:38  铖歌  阅读(312)  评论(0编辑  收藏  举报