大數據技術之_16_Scala學習_05_面向對象編程-中級


第七章 面向對象編程-中級7.1 包7.1.1 Java 中的包7.1.2 Scala 中的包7.1.3 Scala 包的特點概述7.1.4 Scala 包的命名7.1.5 Scala 會自動引入的常用包7.1.6 Scala 包注意事項和使用細節7.1.7 包對象7.1.8 包對象的底層實現機制分析(重點)7.1.9 包對象的注意事項7.2 包的可見性7.2.1 回顧 Java 包的可見性7.2.2 Scala 中包的可見性介紹7.2.3 Scala 中包的可見性和訪問修飾符的使用7.3 包的引入7.3.1 Scala 引入包基本介紹7.3.2 Scala 引入包的細節和注意事項7.4 面向對象編程方法-抽象7.5 面向對象編程三大特征7.5.1 基本介紹7.5.2 封裝的實現步驟7.5.3 快速入門案例7.5.4 Scala 封裝的注意事項和細節7.6 面向對象編程方法-繼承7.6.1 Java 繼承的簡單回顧7.6.2 Scala 的繼承7.6.3 Scala 繼承給編程帶來的便利7.6.4 重寫方法7.6.5 Scala 中類型檢查和轉換7.6.6 Java 中超類的構造7.6.7 Scala 中超類的構造7.6.8 覆寫字段7.6.9 抽象類7.6.10 Scala 抽象類使用的注意事項和細節討論7.6.11 匿名子類7.6.12 繼承層級7.7 作業04


第七章 面向對象編程-中級

7.1 包

7.1.1 Java 中的包

看一個應用場景

回顧-Java 包的三大作用+打包命令+快速入門


示例代碼如下:
package com.atguigu.chapter07.javapackage;

public class DogTest {
    public static void main(String[] args) {
        // 使用 xiaoming 的 Dog
        com.atguigu.chapter07.javapackage.xiaoming.Dog dog01 = new com.atguigu.chapter07.javapackage.xiaoming.Dog();
        // 使用 xiaoqiang 的 Dog
        com.atguigu.chapter07.javapackage.xiaoqiang.Dog dog02 = new com.atguigu.chapter07.javapackage.xiaoqiang.Dog();

        System.out.println("dog01=" + dog01 + "\ndao02=" + dog02);
    }
}

輸出結果如下:

dog01=com.atguigu.chapter07.javapackage.xiaoming.Dog@12a3a380
dao02=com.atguigu.chapter07.javapackage.xiaoqiang.Dog@29453f44

回顧-Java 如何引入包+Java 包的特點

java 包 和 類源碼文件路徑、包文件路徑、.class 文件路徑 的關系圖

7.1.2 Scala 中的包

Scala 包的基本介紹+快速入門


示例代碼如下:
package com.atguigu.chapter07.scalapackage

object CatTest {
  def main(args: Array[String]): Unit = {
    // 使用 xiaoming 的 Cat
    var cat01 = new com.atguigu.chapter07.scalapackage.xiaoming.Cat
    println("cat01=" + cat01)

    // 使用 xiaoming 的 Cat
    var cat02 
new com.atguigu.chapter07.scalapackage.xiaoqiang.Cat
    println("cat02=" + cat02)
  }
}

輸出結果如下:

cat01=com.atguigu.chapter07.scalapackage.xiaoming.Cat@1fbc7afb
cat02=com.atguigu.chapter07.scalapackage.xiaoqiang.Cat@7dc36524

7.1.3 Scala 包的特點概述

scala 包 和 類源碼文件路徑、包文件路徑、.class 文件路徑 的關系圖

7.1.4 Scala 包的命名

7.1.5 Scala 會自動引入的常用包

7.1.6 Scala 包注意事項和使用細節

1、scala 進行 package 打包時,可以有如下形式。【案例演示+反編譯查看】


第三種打包方式示例代碼如下:
// 代碼說明:
// 1. package com.atguigu{}  表示我們創建了包 com.atguigu,在{}中我們可以繼續寫它的子包 scala(即com.atguigu.scala),還可以寫類、特質trait、還可以寫object。
// 2. 即 sacla 支持,在一個文件中,可以同時創建多個包,以及給各個包創建類、trait和object。【超級靈活】
package com.atguigu { // 創建包 com.atguigu
  package scala { // 表示在 com.atguigu 下創建包 scala

    class Person // 表示在 com.atguigu.scala 下創建類 Person
      val name = "Nick"
      def play(message: String): Unit = {
        println(this.name + " " + message)
      }
    }

    object Test { // 表示在 com.atguigu.scala 下創建類 object Test
      def main(args: Array[String]): Unit = {
        println("object Test")
      }
    }

  }

}

2、包也可以像嵌套類那樣嵌套使用(包中有包),這個在前面的第三種打包方式已經講過了,在使用第三種方式時的好處是:程序員可以在同一個文件中,將類(class/object)、trait 創建在不同的包中,這樣就非常靈活了。【案例演示+反編譯查看】
示例代碼如下:

// 代碼說明:
// 1. package com.atguigu{}  表示我們創建了包 com.atguigu,在{}中我們可以繼續寫它的子包 scala(即com.atguigu.scala),還可以寫類、特質trait、還可以寫object。
// 2. 即 sacla 支持,在一個文件中,可以同時創建多個包,以及給各個包創建類、trait和object。【超級靈活】
package com.atguigu { // 創建包 com.atguigu

  class User // 表示在 com.atguigu 下創建類 User

  }

  package scala2 { // 表示在 com.atguigu 下創建包 scala2
    class User {   // 表示在 com.atguigu.scala2 下創建類 User

    }
  }

  package scala {  // 表示在 com.atguigu 下創建包 scala

    class Person // 表示在 com.atguigu.scala 下創建類 Person
      val name = "Nick"
      def play(message: String): Unit = {
        println(this.name + " " + message)
      }
    }

    object Test {  // 表示在 com.atguigu.scala 下創建類 object Test
      def main(args: Array[String]): Unit = {
        println("object Test")
      }
    }

  }

}

3、作用域原則:可以直接向上訪問。即: Scala 中子包中直接訪問父包中的內容,大括號體現作用域。(提示:Java 中子包使用父包的類,需要 import)。在子包和父包 類重名時,默認采用就近原則,如果希望指定使用某個類,則帶上包名即可。【案例演示+反編譯查看】
示例代碼如下:

// 代碼說明:
// 1. package com.atguigu{}  表示我們創建了包 com.atguigu,在{}中我們可以繼續寫它的子包 scala(即com.atguigu.scala),還可以寫類、特質trait、還可以寫object。
// 2. 即 sacla 支持,在一個文件中,可以同時創建多個包,以及給各個包創建類、trait和object。【超級靈活】
package com.atguigu { // 創建包 com.atguigu

  // com.atguigu 下的類 User
  class User // 表示在 com.atguigu 下創建類 User

  }

  package scala2 { // 表示在 com.atguigu 下創建包 scala2
    // com.atguigu.scala2 下的類 User
    class User {   // 表示在 com.atguigu.scala2 下創建類 User

    }
  }

  package scala {  // 表示在 com.atguigu 下創建包 scala

    class Person // 表示在 com.atguigu.scala 下創建類 Person
      val name = "Nick"
      def play(message: String): Unit = {
        println(this.name + " " + message)
      }
    }

    // com.atguigu.scala 下的類 User
    class User {

    }

    object Test {  // 表示在 com.atguigu.scala 下創建類 object Test
      def main(args: Array[String]): Unit = {
        println("object Test")

        // 我們可以直接使用父包的內容
        // 1.如果有同名的類,則采用【就近原則】來使用內容(比如包)
        val user1 = new User
        println("user1=" + user1)
        // 2.如果就是要使用父包的類,則指定路徑即可
        val user2 
new com.atguigu.User
        println("user2=" + user2)
      }
    }

  }

}

輸出結果如下:

object Test
user1=com.atguigu.scala.User@7d417077
user2=com.atguigu.User@7dc36524

4、父包要訪問子包的內容時,需要 import 對應的子包中的類。
5、可以在同一個 xxx.scala 文件中,可以聲明多個並列的 package (建議嵌套的 pakage 不要超過3層)。 【案例演示+反編譯】
6、包名可以相對也可以絕對,比如,訪問 BeanProperty 的絕對路徑是: _root_. scala.beans.BeanProperty,在一般情況下:我們使用相對路徑來引入包,只有當包名沖突時,使用絕對路徑來處理。
示例代碼如下:

object TestBean {
  def main(args: Array[String]): Unit = {
    val m = new Manager("jack")
    println("m=" + m)
  }
}

class Manager(var nameString{
  // 第一種形式:相對路徑引入
  // import scala.beans.BeanProperty
  // @BeanProperty var age: Int = _
  // 第二種形式:和第一種一樣,都是相對路徑引入
  // @scala.beans.BeanProperty var age2: Int = _
  // 第三種形式:是絕對路徑引入,可以解決包名沖突
  @_root_.scala.beans.BeanProperty var age3: Int = _
}

7.1.7 包對象

基本介紹
  包可以包含類、對象和特質 trait,但不能包含函數/方法或變量的定義。這是 Java 虛擬機的局限。為了彌補這一點不足,scala 提供了包對象的概念來解決這個問題。

包對象的應用案例
示例代碼如下:

package com.atguigu {

  // 在包中直接寫方法或者定義變量吧,就會報錯
  // var name = "jack"

  // 1. 在包中直接寫方法,或者定義變量,就錯誤==>使用包對象的技術來解決
  // 2. package object scala 表示創建一個包對象 scala, 他是 com.atguigu.scala 這個包對應的包對象
  // 3. 每一個包都可以有一個包對象
  // 4. 包對象的名字需要和子包一樣
  // 5. 在包對象中可以定義變量,方法
  // 6. 在包對象中定義的變量和方法,就可以在對應的包中使用
  // 7. 在底層這個包對象會生成兩個.class文件 package.class 和 package$.class

  // 每個包都可以有一個【包對象】。你需要在父包(com.atguigu)中定義它,且名稱與子包一樣。
  package object scala {
    var name = "jack"

    def sayOk(): Unit = {
      println("package object sayOk")
    }
  }
  package scala { // 表示在 com.atguigu 下創建子包 scala

    class Test04 {
      def test(): Unit = {
        // 這里的 name 就是包對象 scala 中聲明的 name
        println(name)
        sayOk() // 這個 sayOk()函數 就是【包對象】 scala 中聲明的函數 sayOk
      }
    }

    object TestObj {
      def main(args: Array[String]): Unit = {
        val t = new Test04()
        t.test()
        // 因為 TestObje 和 scala 這個【包對象】在同一包,因此也可以使用
        println("name=" + name)
        sayOk()
      }
    }

  }

}

輸出結果如下:

jack
package object sayOk
name=jack
package object sayOk

7.1.8 包對象的底層實現機制分析(重點)


在底層這個包對象會生成兩個.class文件 package.classpackage$.class

7.1.9 包對象的注意事項

  1、每個包都可以有一個包對象。你需要在父包中定義它。
  2、包對象名稱需要和包名一致,一般用來對包的功能補充。

7.2 包的可見性

7.2.1 回顧 Java 包的可見性

回顧-Java 訪問修飾符基本介紹
java 提供四種訪問控制修飾符號控制方法和變量的訪問權限(范圍):
  1、公開級別:用 public 修飾,對外公開。
  2、受保護級別:用 protected。修飾,對子類和同一個包中的類公開。
  3、默認級別:沒有修飾符號,向同一個包的類公開。
  4、私有級別:用 private 修飾,只有類本身可以訪問,不對外公開。

回顧-Java 中4種訪問修飾符的訪問范圍

回顧-Java 訪問修飾符使用注意事項
  1、修飾符可以用來修飾類中的屬性,成員方法以及類。
  2、只有默認的和 public 才能修飾類,並且遵循上述訪問權限的特點。

7.2.2 Scala 中包的可見性介紹

  在 Java 中,訪問權限分為: public,private,protected 和默認。在 Scala 中,你可以通過類似的修飾符達到同樣的效果。但是使用上有區別。
示例代碼如下:

package com.atguigu.chapter07.visit

object VisitDemo01 {
  def main(args: Array[String]): Unit = {
    val c = new Clerk()
    c.showInfo()
    Clerk.test(c)
  }
}

class Clerk // 伴生類
  var name: String = "jack"         // 在底層是私有,可讀可寫(隱式私有)
  private var sal: Double = 9999.9  // 在底層是私有,只可讀(顯式私有)

  def showInfo(): Unit = {
    // 在本類中可以使用私有的屬性
    println("name=" + name + " sal=" + sal)
  }
}

// 當一個文件中同時出現了 class Clerk 和 object Clerk
// 1、class Clerk  稱為伴生類
// 2、object Clerk 稱為伴生對象
// 因為 scala 的設計者將 static 拿掉了,他就設計了 伴生類 和 伴生對象 的概念
// 將非靜態的內容放在 伴生類 中
// 將靜態的內容放在 伴生對象 中
object Clerk { // 伴生對象
  def test(c: Clerk): Unit = {
    // 這里體現出:在伴生對象中,可以訪問伴生類的私有屬性 c.sal
    println("test() name=" + c.name + " sal=" + c.sal)
  }
}

輸出結果如下:

name=jack sal=9999.9
test() name=jack sal=9999.9

7.2.3 Scala 中包的可見性和訪問修飾符的使用


所有的示例代碼如下:
package com.atguigu.chapter07.visit

object VisitDemo01 {
  def main(args: Array[String]): Unit = {
    val c = new Clerk()
    c.showInfo()
    Clerk.test(c)

    val p = new Person
    println(p.pname)
    println(p.page)
  }
}

class Clerk 
// 伴生類
  var name: String = "jack"         // 在底層是私有,可讀可寫(隱式私有)
  private var sal: Double = 9999.9  // 在底層是私有,只可讀(顯式私有),private 為私有權限,只在 類的內部 和 伴生對象 中可用。

  def showInfo(): Unit = { // 當方法訪問權限為默認時,默認為 public 訪問權限。
    // 在本類中可以使用私有的屬性
    println("name=" + name + " sal=" + sal)
  }
}

// 當一個文件中同時出現了 class Clerk 和 object Clerk
// 1、class Clerk  稱為伴生類
// 2、object Clerk 稱為伴生對象
// 因為 scala 的設計者將 static 拿掉了,他就設計了 伴生類 和 伴生對象 的概念
// 將非靜態的內容放在 伴生類 中
// 將靜態的內容放在 伴生對象 中
object Clerk { // 伴生對象
  def test(c: Clerk): Unit = {
    // 這里體現出:在伴生對象中,可以訪問伴生類的私有屬性 c.sal
    println("test() name=" + c.name + " sal=" + c.sal)
  }
}

class Person {
  // 這里我們增加一個包訪問權限
  // 下面 private[visit] 說明
  // 1、仍然是 private
  // 2、在 visit 包(包括子包)下也可以使用 name,相當於擴大訪問范圍
  private[visit] val pname = "tom" // 增加包訪問權限后,1. private同時起作用。不僅同類可以使用 2. 同時com.atguigu.scala中包下其他類也可以使用
  // 當然,也可以將可見度延展到上層包
  private[chapter07] val page= "25"
}

補充:
// 在 Scala 中,只有兩種訪問控制符:public 和 private
// 在 Scala 中,屬性只有三種修飾符:默認(可讀可寫) 和 private(只可讀) 和 protected(可讀可寫,只能子類訪問),但是底層都是 private
示例代碼如下:

package com.atguigu.chapter07.homework

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

// 在 Scala 中,只有兩種訪問控制符:public 和 private
// 在 Scala 中,屬性只有三種修飾符:默認(可讀可寫) 和 private(只可讀) 和 protected(可讀可寫,只能子類訪問),但是底層都是 private

class Monster {
  var age: Int = 1
  private var name: String = ""
  protected var sal: Double = 0.01
}

7.3 包的引入

7.3.1 Scala 引入包基本介紹

7.3.2 Scala 引入包的細節和注意事項

1、在 Scala 中,import 語句可以出現在任何地方,並不僅限於文件頂部,import 語句的作用一直延伸到包含該語句的塊末尾。這種語法的好處是:在需要時在引入包,縮小 import 包的作用范圍,提高效率。
2、Java 中如果想要導入包中所有的類,可以通過通配符*,Scala 中采用下划線。


3、如果不想要某個包中全部的類,而是其中的幾個類,可以采用選取器(花括號)。

4、如果引入的多個包中含有相同的類,那么可以將不需要的類進行重命名進行區分,這個就是重命名。
5、如果某個沖突的類根本就不會用到,那么這個類可以直接隱藏掉。

7.4 面向對象編程方法-抽象


示例代碼如下:
package com.atguigu.chapter07.abstractdemo

object BankDemo {
  def main(args: Array[String]): Unit = {
    // 開卡
    val account = new Account("gh001"999.9"123456")
    account.query("123456")
    account.withDraw("123456"500)
    account.query("123456")
    account.deposit(200)
    account.query("123456")
  }
}

/**
  * 屬性:
  *   賬號,余額,密碼
  * 方法:
  *   查詢
  *   取款
  *   存款
  */

class Account(InAccountNoStringInBalanceDoubleInPwdString{
  val accountNo = InAccountNo
  var balance = InBalance
  var pwd = InPwd

  // 查詢方法
  def query(InPwd: String): Unit = {
    if (!InPwd.equals(this.pwd)) {
      println("密碼輸入錯誤!")
      return
    }

    printf("賬號為:%s,當前余額是:%.2f"this.accountNo, this.balance)
    println()
  }

  // 取款方法
  def withDraw(InPwd: String, money: Double): Any = {
    if (!InPwd.equals(this.pwd)) {
      println("密碼輸入錯誤!")
      return
    }
    if (money > this.balance) {
      println("余額不足,請充值!")
      return
    }
    this.balance -= money
    money
  }

  // 存款方法
  def deposit(money: Double): Unit = {
    this.balance += money
    println("存款成功!")
  }
}

輸出結果如下:

賬號為:gh001,當前余額是:999.90
賬號為:gh001,當前余額是:499.90
存款成功!
賬號為:gh001,當前余額是:699.90

7.5 面向對象編程三大特征

7.5.1 基本介紹

7.5.2 封裝的實現步驟

7.5.3 快速入門案例

7.5.4 Scala 封裝的注意事項和細節

7.6 面向對象編程方法-繼承

7.6.1 Java 繼承的簡單回顧

Java 繼承的簡單回顧

Java 中繼承的示意圖

7.6.2 Scala 的繼承


示例代碼如下:
package com.atguigu.chapter07.myextends

object Extends01 {
  def main(args: Array[String]): Unit = {
    val stu = new Student
    stu.name = "tom" // 調用了父類 Person 的 stu.name_$eq() 方法相當於 setter 方法
    stu.studying()
    stu.showInfo()
  }
}

class Person {
  var name: String = _
  var age: Int = _

  def showInfo(): Unit 
= {
    println("學生信息如下:")
    println("名字:" + this.name)
  }
}

class Student extends Person {
  def studying(): Unit = {
    println(this.name + " 在學習 scala 中....")
  }
}

輸出結果如下:

tom 在學習 scala 中....
學生信息如下:
名字:tom

7.6.3 Scala 繼承給編程帶來的便利


示例代碼如下:
package com.atguigu.chapter07.myextends

// 說明:
// 1、在 Scala 中,子類繼承了父類的所有屬性,但是父類的 private 的屬性和方法無法訪問。
object Extends02 {
  def main(args: Array[String]): Unit = {
    val sub = new Sub()
    sub.sayOk()

    // sub.test200() // 錯誤,protected 為受保護權限,scala 中受保護權限比 Java 中更嚴格,只能子類訪問,同包無法訪問 (編譯器)。
  }
}

class Base {
  var n1: Int = 1
  protected var n2: Int = 2
  private var n3: Int = 3

  def test100(): Unit = { // 底層是 public
    println("base 100")
  }

  protected def test200(): Unit = { // 底層是 public
    println("base 200")
  }

  private def test300(): Unit = { // 底層是 private
    println("base 300")
  }
}

class Sub extends Base {
  def sayOk(): Unit = {
    this.n1 = 20
    this.n2 = 40
    println("范圍:" + this.n1 + " " + this.n2)
    test100()
    test200()
  }
}

輸出結果如下:

范圍:20 40
base 100
base 200

7.6.4 重寫方法

  說明:scala 明確規定,重寫一個非抽象方法需要用 override 修飾符,調用超類的方法使用 super 關鍵字。
示例代碼如下:

package com.atguigu.chapter07.myextends

object MethodOverride01 {
  def main(args: Array[String]): Unit = {
    val emp = new Emp100
    emp.printName()
  }
}

class Person100 {
  var name: String = "tom"
  def printName() {
    println("Person printName() " + name)
  }
}

// scala 明確規定,重寫一個非抽象方法需要用 override 修飾符,調用超類的方法使用 super 關鍵字。
class Emp100 extends Person100 {
  // 這里需要顯式的使用 override
  override def printName() {
    println("Emp printName() " + name)
    super.printName()
  }
}

輸出結果如下:

Emp printName() tom
Person printName() tom

7.6.5 Scala 中類型檢查和轉換


示例代碼如下:
package com.atguigu.chapter07.myextends

object TypeConvert {
  def main(args: Array[String]): Unit = {
    // 獲取對象類型
    println(classOf[String]) // class java.lang.String

    // 這種是 Java 中反射方式得到對象類型
    val s = "zhangsan"
    println(s.getClass.getName) // java.lang.String

    println(s.isInstanceOf[String]) // true
    println(s.asInstanceOf[String]) // 將 s 顯示轉換成 String

    var p2 = new Person200
    var emp2 = new Emp200
    // 將子類引用給父類(向上轉型,自動)
    p2 = emp2
    // 將父類引用轉成子類引用(向下轉型,需要手動)
    var emp3 = p2.asInstanceOf[Emp200]
    emp3.sayHello()
  }
}

class Person200 {
  var name: String = "tom"

  def printName() {
    println("Person printName() " + name)
  }
}

// scala 明確規定,重寫一個非抽象方法需要用 override 修飾符,調用超類的方法使用 super 關鍵字。
class Emp200 extends Person200 {
  // 這里需要顯式的使用 override
  override def printName() {
    println("Emp printName() " + name)
    super.printName()
  }

  def sayHello(): Unit = {
    println("hello")
  }
}

輸出結果如下:

class java.lang.String
java.lang.String
true
zhangsan
hello

Scala 中類型檢查和轉換的最佳實踐
  類型檢查和轉換的最大價值在於:可以判斷傳入對象的類型,然后轉成對應的子類對象,進行相關操作,這里也體現出多態的特點。
示例代碼如下:

package com.atguigu.chapter07.myextends

object TypeConvertExer {
  def main(args: Array[String]): Unit = {
    // 創建子類對象
    val stu = new Stu
    val worker = new Worker
    test(stu)     // stuId=100
    test(worker)  // workerId=200
  }

  def test(p: Person300): Unit 
= {
    // 如果傳入是 Student 實例,則調用 sayOk
    // 如果傳入是 Worker 實例,則調用 sayHello
    // 進行 p.asInstanceOf[T] 轉換時,要求 p 的引用本身就是指向 T 類型的引用
    if (p.isInstanceOf[Stu]) {
      // 向下轉型
      p.asInstanceOf[Stu].sayOk() // 注意:p.asInstanceOf[Stu] 對 p 的類型沒有任何變化,而是返回的是 Stu 類型
    } else if (p.isInstanceOf[Worker]) {
      p.asInstanceOf[Worker].sayHello()
    } else {
      println("轉換錯誤")
    }
  }
}

class Person300 {
  var name: String = "tom"

  def printName() {
    println("name=" + name)
  }
}

class Stu extends Person300 {
  var stuId: Int = 100

  def sayOk(): Unit = {
    println("stuId=" + this.stuId)
  }
}

class Worker extends Person300 {
  var workerId: Int = 200

  def sayHello(): Unit = {
    println("workerId=" + this.workerId)
  }
}

輸出結果如下:

stuId=100
workerId=200

7.6.6 Java 中超類的構造

回顧-Java 中超類的構造
示例代碼如下:

package com.atguigu.chapter07.myextends;

public class JavaBaseConstractor {
    public static void main(String[] args) {
        B b1 = new B();
        B b2 = new B("tom");
    }
}

class A {
    public A() {
        System.out.println("A()");
    }
    public A(String name) {
        System.out.println("A(String name) " + name);
    }
}
class B extends A {
    public B() {
        // 這里會隱式調用super(); 就是無參的父類構造器A()
        System.out.println("B()");
    }
    public B(String name) {
        super(name);
        System.out.println("B(String name) " + name);
    }
}

輸出結果如下:

A()
B()
A(String name) tom
B(String name) tom

說明:
  從代碼可以看出:在 Java 中,創建子類對象時,子類的構造器總是去調用一個父類的構造器(顯式或者隱式調用)。

7.6.7 Scala 中超類的構造

Scala 超類的構造說明
  1、類有一個主構器和任意數量的輔助構造器,而每個輔助構造器都必須先調用主構造器(也可以是間接調用),這點在前面我們說過了。
  2、只有子類的主構造器可以調用父類的構造器(主和輔均可)。子類的輔助構造器不能直接調用父類的構造器。在 Scala 的子類的構造器中,你不能調用 super(params)。
完整示例代碼如下:

package com.atguigu.chapter07.myextends

object ScalaBaseConstractor {
  def main(args: Array[String]): Unit = {
    // 分析一下它的執行流程
    // 1、因為 scala 遵守先構建父類部分 extends Person500()
    // 2、Person...
    // 3、Emp... (Emp500的主構造器)
    val emp1 = new Emp500()

    println("----------")
    // 分析一下它的執行流程
    // 1、因為 scala 遵守先構建父類部分 extends Person500()
    // 2、Person...
    // 3、Emp.... (Emp500的主構造器)
    // 4、Emp輔助構造器
    val emp2 = new Emp500("lucy")

    println("----------")
    // 分析一下它的執行流程
    // 1、因為 scala 遵守先構建父類部分 extends Person600(),由父類的輔助構造器調用該父類的帶參主構造器
    // 2、Person...
    // 3、Emp.... (Emp600的主構造器)
    // 4、Emp輔助構造器
    val emp3 = new Emp600("lucy")

    println("----------")
    // 分析一下它的執行流程
    // 1、因為 scala 遵守先構建父類部分 extends Person700(eName),調用該父類的帶參主構造器
    // 2、Person...
    // 3、Emp.... (Emp700的主構造器)
    val emp4 = new Emp700("lucy"20)
  }
}

class Person500 {
  var name = "zhangsan"
  println("Person...")
}

class Emp500 extends Person500 {
  println("Emp...")

  def this(name: String) {
    this // 該子類的輔助構造器必須調用該子類的主構造器
    this.name = name
    println("Emp輔助構造器")
  }
}

class Person600(pName: String) 
{
  var name = pName
  println("Person...")

  def this() 
{
    this("默認的名字"// 該父類的輔助構造器必須調用該父類的帶參主構造器
    println("默認的名字")
  }
}

class Emp600 extends Person600 {
  println("Emp...")

  def this(name: String) {
    this // 該子類的輔助構造器必須調用該子類的主構造器
    this.name = name
    println("Emp輔助構造器")
  }
}

class Person700(pName: String) 
{
  var name = pName
  println("Person...")

  def this() 
{
    this("默認的名字"// 該父類的輔助構造器必須調用該父類的帶參主構造器
    println("默認的名字")
  }
}

class Emp700(eName:StringeAgeIntextends Person700(eName{
  println("Emp...")

  def this(name: String) {
    this("mary"10// 該子類的輔助構造器必須調用該子類的主構造器
    this.name = name
    println("Emp輔助構造器")
  }
}

輸出結果如下:

Person...
Emp...
----------
Person...
Emp...
Emp輔助構造器
----------
Person...
默認的名字
Emp...
Emp輔助構造器
----------
Person...
Emp...

7.6.8 覆寫字段

Java 另一重要特性: 動態綁定 機制
示例代碼如下:

package com.atguigu.chapter07.myextends;

public class JavaDaynamicBind {
    public static void main(String[] args) {
        // 提出 java 的動態綁定機制
        // 1. 當調用對象方法的時候,該方法會和該對象的內存地址綁定。
        // 2. 當調用對象屬性時,沒有動態綁定機制,哪里聲明,那里使用。
        AA a = new BB();                // BB均不注銷   BB只注銷sum()  BB注銷sum()和注銷sum1()
        System.out.println(a.sum());    // 40           30              30
        System.out.println(a.sum1());   // 30           30              20

        //
        /*
        java中多態中對成員的訪問的特點 = 動態綁定
        成員變量:
            編譯看左邊,運行看左邊。
        成員方法:
        編譯看左邊,運行看右邊。
        靜態方法:
        編譯看左邊,運行看左邊。
        */

    }
}

class AA {
    public int i = 10;
    public int sum() {
        return getI() + 10;
    }
    public int sum1() {
        return i + 10;
    }
    public int getI() {
        return i;
    }
}

class BB extends AA {
    public int i = 20;
/*    public int sum() {
        return i + 20;
    }*/

/*    public int sum1() {
        return i + 10;
    }*/

    public int getI() {
        return i;
    }
}

Scala 覆寫字段快速入門
示例代碼如下:

package com.atguigu.chapter07.myextends

object ScalaFiledOverride {
  def main(args: Array[String]): Unit = {
    val obj1: AAA = new BBB
    val obj2: BBB = new BBB
    println("obj1.age=" + obj1.age) // 動態綁定機制生效
    println("obj2.age=" + obj2.age)
  }
}

class AAA 
{
  val age: Int = 10 // 底層是 public int age() 方法
}

class BBB extends AAA {
  override val age: Int = 20 // 底層是 public int age() 方法
}

輸出結果如下:

obj1.age=20
obj2.age=20

反編譯后的代碼:

public class AAA {
  private final int age = 10;
  public int age() 
    return this.age;
  }
}

public class BBB extends AAA {
  private final int age = 20;
  public int age() 
    return this.age;
  }
}

覆寫字段的注意事項和細節


1、def 只能重寫另一個 def (即:方法只能重寫另一個方法)。
2、val 只能重寫另一個 val 屬性 或 重寫不帶參數的 def。
示例代碼如下:
package com.atguigu.chapter07.myextends

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

  }
}

// 2、val 只能重寫另一個 val 屬性。
class AAAA {
  // var name: String = "" // 底層會生成 public String name() 和 public String name_$seq()
  val name: String = "" // 底層只會生成 public String name()
}

class BBBB extends AAAA {
  override val name: String = "tom" // 底層只會生成 public String name()
}

示例代碼如下:

package com.atguigu.chapter07.myextends

object ScalaFiledOverrideDetail02 {
  def main(args: Array[String]): Unit = {
    val b1 = new BBBBB
    println("b1.sal=" + b1.sal) // b1.sal=20

    val b2:AAAAA 
new BBBBB // 動態綁定機制生效
    println("b2.sal=" + b2.sal()) // b2.sal=20
  }
}

// 2、val 只能重寫重寫不帶參數的 def。
class AAAAA {
  def sal(): Int = { // 底層只會生成 public int sal()
    return 10
  }
}
class BBBBB extends AAAAA {
  override val sal: Int = 20 // 底層只會生成 public int sal()
}

3、var 只能重寫另一個抽象的 var 屬性。


示例代碼如下:
package com.atguigu.chapter07.myextends

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

// 1、抽象屬性:聲明未初始化的變量(字段/屬性)就是抽象的屬性,抽象屬性在抽象類中。
// 2、對於抽象的屬性,在底層不會生成對應的屬性聲明,而是生成兩個對應的抽象方法。
// public abstract String name();
// public abstract void name_$eq(String paramString);
abstract class A03 {
  var name: String
  val age: Int = 10 // val修飾,底層只生成 public int age()
}

// 1、我們在子類中重寫父類的抽象屬性,本質上是實現了抽象方法,生成了兩個 public 方法
// public String name()
// public void name_$eq(String x$1)
// 2、如果是覆寫一個父類的抽象屬性,那么 override 關鍵字可省略。
// 3、如果是覆寫一個父類的非抽象屬性,那么 override 關鍵字不可省略。
class B03 extends A03 {
  // var name: String = _ // 如果是覆寫一個父類的抽象屬性,那么 override 關鍵字可省略。
  override var name: String = _
  override val age: Int = 20 // val修飾,底層只生成 public int age()
}

7.6.9 抽象類


示例代碼如下:
package com.atguigu.chapter07.myextends

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

abstract class Animal {
  var name: String // 抽象的字段
  var age: Int // 抽象的字段
  var color: String = "black" // 普通字段

  // def cry // 抽象的方法的小括號可以省略
  def cry() // 抽象的方法,不需要標記 abstract,否則報錯
}

抽象類基本語法

7.6.10 Scala 抽象類使用的注意事項和細節討論


示例代碼如下:
package com.atguigu.chapter07.myextends

object AbstractClassDetail01 {
  def main(args: Array[String]): Unit = {
    // 1、抽象類不能被實例。但是有一種情況:當動態實現抽象類的所有抽象方法時,抽象類也就被實例化了。本質是該抽象類的匿名子類實現了該抽象類。
    val a1 = new Animal01 {
      override def eat(): Unit = {
        println("eat...")
      }
    }
    a1.eat()

    val a5 = new Cat
    a5.name = "小呆萌"
    a5.eat()
  }
}

abstract class Animal01 {
  def eat()
}

// 2、抽象類不一定要包含 abstract 方法。也就是說,抽象類可以沒有 abstract 方法。
abstract class Animal02 
{
  // 7、抽象類中可以有實現的方法。
  def eat(): Unit = {
    println("eat...")
  }
}

// 5、如果一個類繼承了抽象類,則它必須實現抽象類的所有抽象方法和抽象屬性,除非它自己也聲明為 abstract 類。
abstract class Animal05 {
  var name:String
  def eat()
}

class Cat extends Animal05 
{
  // 9、如果是覆寫一個父類的抽象屬性,那么 override 關鍵字可省略。
  override var name: String = _
  // 8、子類重寫抽象方法不需要 override,寫上也不會錯。
  override def eat(): Unit = {
    println(this.name + " eat fash")
  }
}

輸出結果如下:

eat...
小呆萌 eat fash

7.6.11 匿名子類


示例代碼如下:
package com.atguigu.chapter07.myextends;

public class NoNameDemo01 {
    public static void main(String[] args) {
        A2 a = new A2() {
            @Override
            public void cry() {
                System.out.println("嗚嗚");
            }
        };
        a.cry();

    }
}

abstract class A2 {
    abstract public void cry();
}

輸出結果如下:

嗚嗚

示例代碼如下:

package com.atguigu.chapter07.myextends

object NoNameDemo02 {
  def main(args: Array[String]): Unit = {
    var monster = new Monster {
      override var name: String = "牛魔王"

      override def cry(): Unit = {
        println(this.name + " 哞哞")
      }
    }
    monster.cry()
  }
}

abstract class Monster {
  var name: String

  def cry()
}

輸出結果如下:

牛魔王 哞哞

7.6.12 繼承層級

Scala 繼承層級一覽圖


繼承層級圖小結

7.7 作業04

1、編寫一個 Time 類,加入只讀屬性 hours 和 minutes,和一個檢查某一時刻是否早於另一時刻的方法 before(other: Time): Boolean。Time 對象應該以 new Time(hrs, min) 方式構建。
示例代碼如下:

package com.atguigu.chapter07.homework

/**
  * 1、編寫一個 Time 類,加入只讀屬性 hours 和 minutes,和一個檢查某一時刻是否早於另一時刻的方法 before(other: Time): Boolean。
  * Time 對象應該以 new Time(hrs, min) 方式構建。
  */

object Exercise01 {
  def main(args: Array[String]): Unit = {
    // 測試
    val cur = new Time(1020)
    val other = new Time(1020)
    println(cur.before(other)) // 結果根據輸入的對象不同而定
  }
}

class Time(hrsIntminInt// 主構造器
  private val hours: Int = hrs // 只讀屬性
  private val minutes: Int = min // 只讀屬性

  def before(other: Time): Boolean = { // 方法
    if (hours < other.hours) { // 如果當前的小時數小於other
      true
    } else if (hours > other.hours) { // 如果當前的小時數大於other
      false
    } else { // 小時數相等,判斷分鍾
      if (minutes < other.minutes) true else false
    }
  }

}

2、創建一個 Student 類,加入可讀寫的 JavaBeans 屬性 name (類型為 String)和 id (類型為 Long)。
(1)有哪些方法被生產?(用 javap 查看,該指令可以查看 .class 文件的反編譯的方法聲明,還可以看到反匯編代碼)
(2)你可以在 Scala 中調用 JavaBeans 的 getter 和 setter 方法嗎?

// 先進行編譯的命令
scalac Student.scala
// 查看反編譯的方法聲明的命令
javap Student
// 查看反匯編代碼的命令
javap -c Student

示例代碼如下:

import scala.beans.BeanProperty

class Student {
  // 讀寫屬性
  @BeanProperty var name: String = _
  @BeanProperty var id: Long = _
}

演示截圖如下:

3、練習使用包的各種聲明方式,並查看他們的不同。
答:一共有三種導入方式。在一般情況下:我們使用相對路徑來引入包,只有當包名沖突時,使用絕對路徑來處理。
示例代碼如下:

object TestBean {
  def main(args: Array[String]): Unit = {
    val m = new Manager("jack")
    println("m=" + m)
  }
}

class Manager(var nameString{
  // 第一種形式:相對路徑引入
  // import scala.beans.BeanProperty
  // @BeanProperty var age: Int = _
  // 第二種形式:和第一種一樣,都是相對路徑引入
  // @scala.beans.BeanProperty var age2: Int = _
  // 第三種形式:是絕對路徑引入,可以解決包名沖突
  @_root_.scala.beans.BeanProperty var age3: Int = _
}

4、編寫一段程序,將 Java 哈希映射(Java 中的 HashMap)中的所有元素拷貝到 Scala 哈希映射(Scala 中的 HashMap)。用引入語句重命名這兩個類。
示例代碼如下:

package com.atguigu.chapter07.homework

// 說明:
// 1、當我們繼承了App后,就可以直接在這個類中執行代碼,不需要再寫 main 入口了。
object Exercise03 extends App {

  import java.util.{HashMap => JavaHashMap} // 重命名 Java 中的 HashMap
  import collection.mutable.{HashMap => ScalaHashMap, _} // 重命名 Scala 中的 HashMap

  val javaMap = new JavaHashMap[Int, String] // 創建 Java 的 HashMap,其中 [Int, String] 是泛型
  javaMap.put(1"One");  // 加入了四對 key-val
  javaMap.put(2"Two");
  javaMap.put(3"Three");
  javaMap.put(4"Four");

  val scalaMap = new ScalaHashMap[Int, String] // 創建 Scala 的 HashMap,其中 [Int, String] 是泛型

  // 說明
  // 1、javaMap.keySet().toArray,這里是將 javaMap 的 key 轉成數組
  // 2、key.asInstanceOf[Int] 將 key 強轉成 Int 類型
  // 3、javaMap.get(key),得到這個 key 對應 value
  // 4、(key.asInstanceOf[Int] -> javaMap.get(key))  是 key -> value
  // 5、+= 將 key -> value 加入(拷貝)到 scalaMap
  for (key <- javaMap.keySet().toArray) {
    scalaMap += (key.asInstanceOf[Int] -> javaMap.get(key))
  }
  println(scalaMap) // Map(2 -> Two, 4 -> Four, 1 -> One, 3 -> Three)
  println(scalaMap.mkString(" ")) // 2 -> Two 4 -> Four 1 -> One 3 -> Three
}

輸出結果如下:

Map(2 -> Two, 4 -> Four, 1 -> One, 3 -> Three)
2 -> Two 4 -> Four 1 -> One 3 -> Three


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM