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

8.1 静态属性和静态方法

  8.1.1 静态属性-提出问题 

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

      小孩堆雪人

  8.1.2 基本介绍

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

        Scala语言是彻底面向对象(万物皆对象)的语言,因此并无静态的操做(即在Scala中没有静态的概念)。可是为了可以和Java语言交互(由于Java中有静态概念),就产生了一种特殊的对象来模拟类对象,咱们称之为类的伴生对象。这个类的全部静态内容均可以放置在它的伴生对象中声明和调用sql

  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) 伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致oracle

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

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

      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中内部类的语法操做,咱们将这种方法称之为 类型投影(即:忽略对象的建立方式,只考虑类型)

相关文章
相关标签/搜索