Cris的Scala筆記整理(七):面向對象

NO IMAGE

7. 面向對象(重點)

7.1 Scala 面向對象基礎

[修飾符] class 類名 {

類體

}

  1. scala語法中,類並不聲明為public,所有這些類都具有公有可見性(即默認就是public)

  2. 一個Scala源文件可以包含多個類

定義一個最簡單的類

object Demo {
  def main(args: Array[String]): Unit = {
    var man = new Man
    man.name = "cris"
    man.age = 12
    println(man.name + "----" + man.age) // cris----12
  }

}

class Man {
  var name = ""
  var age = 0
}

反編譯對應的 class 文件

Cris的Scala筆記整理(七):面向對象

屬性

屬性是類的一個組成部分,一般是值數據類型,也可是引用類型

Cris的Scala筆記整理(七):面向對象

  def main(args: Array[String]): Unit = {
    val man = new Man()
    val pc = new PC
    man.pc = pc
    man.pc.brand = "惠普"
    // man.pc().brand()
    println(man.pc.brand) // 惠普
  }

class Man {
  var name = "" // 手動設置初始值,此時可以省略成員屬性的數據類型聲明
  var age = 0
  var pc: PC = _ // _ 表示讓 Scala 自動賦默認值,此時聲明帶上成員屬性的數據類型,否則編譯器無法確定默認值
}

class PC {
  var brand: String = _
}

Cris的Scala筆記整理(七):面向對象

練習

  1. 針對 for(int i = 10;i>0;i–){System.out.println(i)} 翻譯成 Scala 代碼

    object Practice {
      def main(args: Array[String]): Unit = {
        for (i <- 0.to(10).reverse) {
          print(i + "\t") // 10  9  8  7  6  5  4  3  2  1  0  
        }
      }
    }
    
  2. 使用過程重寫上面的 Scala 代碼

    def func(x: Int) {
      for (i <- 0 to x reverse) {
        print(i + "\t")
      }
    }
    
  3. 編寫一個for循環,計算字符串中所有字母的Unicode代碼(toLong方法)的乘積。舉例來說,”Hello”中所有字符串的乘積為9415087488L

    def cal(str:String): Unit ={
      var result = 1L
      for(x <- str){
        result*=x.toLong
      }
      print(result)
    }
    
  4. 使用 StringOps 的 foreach 方法重寫上面的代碼

    var r2 = 1L
    // _ 可以理解為字符串的每一個字符
    "Hello".foreach(r2 *= _.toLong)
    print(r2)
    
  5. 使用遞歸解決上面求字符串每個字符 Unicode 編碼乘積的問題

    def recursive(str: String): Long = {
      if (str.length == 1) str.charAt(0).toLong
      /*drop(n)從索引為 1 開始切片到結尾*/
      else str.take(1).charAt(0).toLong * recursive(str.drop(1))
    }
    
  6. 編寫函數計算 x^n,其中 n 是整數(負數,0,正數),請使用遞歸解決

    def pow(x: Int, n: Int): Double = {
      if (n == 0) 1
      else if (n < 0) {
        1.0 / x * pow(x, n + 1)
      } else {
        x * pow(x, n - 1)
      }
    }
    

對象

val | var 對象名 [:類型] = new 類型()

  1. 如果我們不希望改變對象的引用(即:內存地址), 應該聲明為val 性質的,否則聲明為var, scala設計者推薦使用val ,因為一般來說,在程序中,我們只是改變對象屬性的值,而不是改變對象的引用

  2. scala在聲明對象變量時,可以根據創建對象的類型自動推斷,所以類型聲明可以省略,但當類型和後面new 對象類型有繼承關係即多態時,就必須寫

方法

Scala中的方法其實就是函數,只不過一般將對象中的函數稱之為方法

def 方法名(參數列表) [:返回值類型] = {

​ 方法體

}

練習

  1. 嵌套循環打印圖形

    def func1(): Unit ={
        for (i <- 1 to 4; j <- 1 to 3) {
            if (j == 3) println("*")
            else print("*\t")
        }
    }
    

    Cris的Scala筆記整理(七):面向對象

  2. 計算矩形的面積

    class Test {
      def area(): Double = {
        (this.width * this.length).formatted("%.2f").toDouble
      }
    
    
      var width: Double = _
      var length: Double = _
    

構造器

java 的構造器回顧

[修飾符] 方法名(參數列表){

構造方法體

}

  1. 在Java中一個類可以定義多個不同的構造方法,構造方法重載

  2. 如果程序員沒有定義構造方法,系統會自動給類生成一個默認無參構造方法(也叫默認構造器)

3)一旦定義了自己的構造方法,默認的構造方法就覆蓋了,就不能再使用默認的無參構造方法,除非顯示的定義一下,即: Person(){}

Scala 構造器

和Java一樣,Scala構造對象也需要調用構造方法,並且可以有任意多個構造方法。

Scala類的構造器包括: 主構造器 和 輔助構造器

基礎語法

class 類名(形參列表) { // 主構造器

// 類體

def this(形參列表) { // 輔助構造器

}

def this(形參列表) { //輔助構造器可以有多個…

}

}

簡單示例

abstract class Dog {
  var name = ""
  var age = 0
  val color: String

  def this(name: String, age: Int) {
    this()
    this.name = name
    this.age = age
  }

  def eat(): Unit = {
    println("吃狗糧")
  }

  def run()
}
class Cat(var name: String, val color: String) {
  println("constructor is processing")

  def describe: String = name + "--" + color
}
  def main(args: Array[String]): Unit = {
	var cat = new Cat("tom", "gray")
    println(cat.describe)
    var cat2 = new Cat("jack", "red")
    println(cat2.describe)
  }

細節

  1. Scala構造器作用是完成對新對象的初始化,構造器沒有返回值。

  2. 主構造器的聲明直接放置於類名之後 [反編譯]

  3. 主構造器會執行類定義中的所有語句,這裡可以體會到Scala的函數式編程和麵向對象編程融合在一起,即:構造器也是方法(函數),傳遞參數和使用方法和前面的函數部分內容沒有區別

  4. 如果主構造器無參數,小括號可省略,構建對象時調用的構造方法的小括號也可以省略

  5. 輔助構造器名稱為this(這個和Java是不一樣的),多個輔助構造器通過不同參數列表進行區分, 在底層就是java的構造器重載,輔助構造器第一行函數體必須為 this.主構造器

abstract class Dog {
  var name = ""
  var age = 0
  val color: String

  def this(name: String, age: Int) {
    this()
    this.name = name
    this.age = age
  }

  def eat(): Unit = {
    println("吃狗糧")
  }

  def run()
}

6)) 如果想讓主構造器變成私有的,可以在()之前加上private,這樣用戶只能通過輔助構造器來構造對象了,說明:因為Person3的主構造器是私有,因此就需要使用輔助構造器來創建對象

class Car private(){}
  1. 輔助構造器的聲明不能和主構造器的聲明一致,會發生錯誤

屬性高級

  1. Scala類的主構造器函數的形參未用任何修飾符修飾,那麼這個參數是局部變量

  2. 如果參數使用val關鍵字聲明,那麼Scala會將參數作為類的私有的只讀屬性使用

  3. 如果參數使用var關鍵字聲明,那麼那麼Scala會將參數作為類的成員屬性使用,並會提供屬性對應的xxx()[類似getter]/xxx_$eq()[類似setter]方法,即這時的成員屬性是私有的,但是可讀寫

class Counter {

  /*1. 有公開的 getter 和 setter 方法*/
  var count = 0
  /*2. 私有化 getter 和 setter,可以手動提供 setter 和 getter*/
  private var number = 1
  /*3. 只能被訪問getter,無法修改setter,final 修飾的 age 屬性*/
  val age = 12
  /*4. 對象級別的私有*/
  private[this] var length = 12

  def compare(other: Counter): Boolean = other.number > number

  //  def compareLength(other: Counter): Boolean = length > other.length

  def increase(): Unit = {
    number += 1
  }

  /*無參方法可以省略(),{}也可以省略*/
  def current: Int = number
}

def main(args: Array[String]): Unit = {
    var c = new Counter()
    c.count = 3
    println(c.count) // 3

    c.increase()
    println(c.current) // 2

    println(c.age) // 12
}

如果在主構造器中為屬性設置了默認值,那麼就不必在函數體內再去聲明屬性以及賦值了,大大簡化代碼的書寫

def main(args: Array[String]): Unit = {
	val dog = new Dog()
    println(dog.name) // cris
    println(dog.age)  // 10
  }
}

class Dog(var name :String= "cris",var age:Int = 10){
    
}

JavaBean 註解

JavaBeans規範定義了Java的屬性是像getXxx()和setXxx()的方法。許多Java工具(框架)都依賴這個命名習慣。為了Java的互操作性。將Scala字段加@BeanProperty時,這樣會自動生成規範的 setXxx/getXxx 方法。這時可以使用 對象.setXxx() 和 對象.getXxx() 來調用屬性

給某個屬性加入@BeanPropetry註解後,會生成getXXX和setXXX的方法

並且對原來底層自動生成類似xxx(),xxx_$eq()方法,沒有衝突,二者可以共存

對象創建流程分析

請針對以下代碼簡述對象創建流程

class Bike {
  var brand = ""
  var color = ""

  def this(brand: String, color: String) {
    this
    this.brand = brand
    this.color = color
  }
}

def main(args: Array[String]): Unit = {
   var bike = new Bike("ofo", "黃色")   
}
  1. 加載類信息(屬性信息,方法信息)

  2. 在堆中,給對象開闢空間

  3. 調用主構造器對屬性進行初始化

  4. 使用輔助構造器對屬性進行初始化

  5. 把對象空間的地址,返回給 bike 引用

7.2 面向對象進階

包(難點)

回顧 Java 的包知識

  1. 作用

    1. 區分相同名字的類

    2. 當類很多時,可以很好的管理

    3. 控制訪問範圍

  2. 打包基本語法

    package com.cris;

  3. 打包的本質分析

    實際上就是創建不同的文件夾保存類文件

  4. 示例代碼

    先在不同的包下建立同名的類

    Cris的Scala筆記整理(七):面向對象

    如果想要在一個類中同時使用上面的兩個 Pig,Java 的解決方式如下:

        public static void main(String[] args) {
            Pig pig1 = new Pig();
            cris.package2.Pig pig2 = new cris.package2.Pig();
    //        pig1.getClass() = class cris.package1.Pig
            System.out.println("pig1.getClass() = " + pig1.getClass());
    //        pig2.getClass() = class cris.package2.Pig
            System.out.println("pig2.getClass() = " + pig2.getClass());
        }
    

    再來看看我們的源碼所在路徑和字節碼文件所在路徑,都是一一對應的

    Cris的Scala筆記整理(七):面向對象

    Cris的Scala筆記整理(七):面向對象

    Java 要求源碼所在路徑和字節碼文件所在路徑必須保持一致,如果我們此時去修改源碼的打包路徑

    Cris的Scala筆記整理(七):面向對象

  5. 基本語法

    import java.awt.* or import java.util.List

  6. 注意事項:java中包名和源碼所在的系統文件目錄結構要一致,並且編譯後的字節碼文件路徑也和包名保持一致

接著看看 Scala 是如何處理的

我們使用 Scala 重寫上面的 Java 包案例

Cris的Scala筆記整理(七):面向對象

def main(args: Array[String]): Unit = {
  var b1 = new cris.package1.Bird1
  var b2 = new cris.package2.Bird2
  //    class cris.package1.Bird1
  println(b1.getClass)
  //    class cris.package2.Bird2
  println(b2.getClass)
}

此時我們如果修改了 Bird1 的打包路徑

Cris的Scala筆記整理(七):面向對象

再看看源代碼和字節碼文件所在的路徑

Cris的Scala筆記整理(七):面向對象

Cris的Scala筆記整理(七):面向對象

Scala 的包

和Java一樣,Scala中管理項目可以使用包,但Scala中的包的功能更加強大,使用也相對複雜些

  1. 基本語法
    package 包名

  2. Scala包的三大作用(和Java一樣)

    1. 區分相同名字的類
    2. 當類很多時,可以很好的管理類
    3. 控制訪問範圍
  3. Scala中包名和源碼所在的系統文件目錄結構要可以不一致,但是編譯後的字節碼文件路徑包名會保持一致(這個工作由編譯器完成)

  4. 圖示

    Cris的Scala筆記整理(七):面向對象

  5. 命名規範

    只能包含數字、字母、下劃線、小圓點.,但不能用數字開頭, 也不要使用關鍵字

    一般是小寫字母+小圓點一般是 com.公司名.項目名.業務模塊名

  6. Scala 自動 import 的包有:java.lang.*,scala,Predef 包

Scala 打包細節(難點)

  • 常用的兩種打包形式

    • 源代碼的路徑和字節碼文件路徑保持一致

    • 源代碼的路徑和字節碼文件路徑不一致

    • 上面的演示中已經很清楚的展示了 Scala 包的這一特點,我們繼續用下面代碼演示 Scala 包的嵌套

      Cris的Scala筆記整理(七):面向對象

      我們在 Detail 類文件中寫入以上非常奇怪的代碼,編譯運行後再查看源代碼和字節碼文件的位置

      Cris的Scala筆記整理(七):面向對象

      Cris的Scala筆記整理(七):面向對象

      Cris的Scala筆記整理(七):面向對象

      進一步印證了 Scala 中源文件和字節碼文件路徑可以不一致

  • 包也可以像嵌套類那樣嵌套使用(包中有包), 見上面圖示。好處是:程序員可以在同一個文件中,將類(class / object)、trait 創建在不同的包中,非常靈活

  • 作用域原則:可以直接向上訪問。即: Scala中子包中直接訪問父包中的內容, 大括號體現作用域。(提示:Java中子包使用父包的類,需要import)。在子包和父包 類重名時,默認採用就近原則,如果希望指定使用某個類,則帶上包名即可

    示例代碼

    package com.cris {
    
      class Apple {
    
      }
      package scala {
        
        class Apple {
    
        }
    
        object Boy {
          def main(args: Array[String]): Unit = {
            /*1. Scala 中子包可以直接訪問父包的內容;2. 子包和父包的類重名,默認採取就近原則;3. 可以帶上類的路徑名指定使用該類*/
            val apple = new Apple
            val apple2 = new com.cris.Apple
            //        class com.cris.scala.Apple
            println(apple.getClass)
            //        class com.cris.Apple
            println(apple2.getClass)
          }
        }
      }
    }
    
  • 父包要訪問子包的內容時,需要import對應的類

    package com.cris {
    	
      import com.cris.scala.Apple
    
      object Apple{
        def main(args: Array[String]): Unit = {
            // 推薦只在使用的時候再引用,控制作用域
    	  import com.cris.scala.Apple
          val apple = new Apple()
    //      class com.cris.scala.Apple
          println(apple.getClass)
        }
      }
    
      package scala {
    
        class Apple {
    
        }
      }
    }-
    
  • 可以在同一個.scala文件中,聲明多個並列的package(建議嵌套的pakage不要超過3層)

    Cris的Scala筆記整理(七):面向對象

包對象

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

參見如下代碼

package com.cris {

  // 不能直接在 package 中定義函數和變量
  //  var name = "cris"

  /**
    * 包對象的名字需要和包名一致
    * package object emp 會在 com.cris.emp 包下生成&emsp;package.class 和&emsp;package$.class
    */
  package object emp {
    def eat(): Unit = {
      println("eat")
    }

    val salary = 1000.0
  }

  package emp {

    object test {
      def main(args: Array[String]): Unit = {
        eat() // eat=》等價於使用了&emsp;package$.class 中的&emsp;MODULE$.eat()
        println(salary) // 1000.0=>&emsp;等價於使用了&emsp;package$.class 中的 MODULE$.salary()
      }
    }
  }
}

Cris的Scala筆記整理(七):面向對象

使用反編譯工具打開瞧瞧

Cris的Scala筆記整理(七):面向對象

具體的執行流程第二章節已經解釋過,這裡不再贅述

注意事項:

  1. 每個包都可以有一個包對象,但是需要在父包中定義它
  2. 包對象名稱需要和包名一致,一般用來對包(裡面的類)的功能做補充

包的可見性

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

  1. 當屬性訪問權限為默認時,從底層看屬性是private的,但是因為提供了xxx_$eq()[類似setter]/xxx()[類似getter] 方法,因此從使用效果看是任何地方都可以訪問)

  2. 當方法訪問權限為默認時,默認為public訪問權限

  3. private為私有權限,只在類的內部和伴生對象中可用

示例:

Cris的Scala筆記整理(七):面向對象

Cris的Scala筆記整理(七):面向對象

  1. protected為受保護權限,scala中受保護權限比Java中更嚴格,只能子類訪問,同包無法訪問

  2. 在scala中沒有public關鍵字,即不能用public顯式的修飾屬性和方法。

包訪問權限(表示屬性有了限制。同時增加了包的訪問權限),這點和Java不一樣,體現出Scala包使用的靈活性

Cris的Scala筆記整理(七):面向對象

包的引入

細節說明

  1. 在Scala中,import語句可以出現在任何地方,並不僅限於文件頂部,import語句的作用一直延伸到包含該語句的塊末尾。這種語法的好處是:在需要時在引入包,縮小import 包的作用範圍,提高效率

    示例如下:

    Cris的Scala筆記整理(七):面向對象

  2. Java中如果想要導入包中所有的類,可以通過通配符*,Scala中採用下 _

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

    Cris的Scala筆記整理(七):面向對象

  4. 如果引入的多個包中含有相同的類,那麼可以將不需要的類進行重命名進行區分,這個就是重命名

    Cris的Scala筆記整理(七):面向對象

  5. 或者使用 import java.util.{HashMap => _ } 對衝突的包進行隱藏

練習

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

    object Practice {
      def main(args: Array[String]): Unit = {
        val time1 = new Time(4, 12)
        val result = time1.before(new Time(4, 14))
        println(result)
      }
    }
    
    class Time(val hour: Int, val minute: Int) {
      
      def before(other: Time) = {
        if (this.hour < other.hour) true
        else if (this.hour > other.hour) false
        else if (this.hour == other.hour) {
          if (this.minute < other.minute) true
          else if (this.minute > other.minute) false
          else false
        }
      }
    }
    
  2. 創建一個Student類,加入可讀寫的JavaBeans屬性name(類型為String)和id(類型為Long)。有哪些方法被生產?(用javap查看。)你可以在Scala中調用JavaBeans的getter和setter方法嗎?

    object Practice {
      def main(args: Array[String]): Unit = {
        var s = new Student
        println(s.getName)
        println(s.age)
      }
    }
    
    class Student {
      @BeanProperty var name = "好學生"
      @BeanProperty var age = 0
    
    }
    

    Cris的Scala筆記整理(七):面向對象

  3. 編寫一段程序,將Java哈希映射中的所有元素拷貝到Scala哈希映射。用引入語句重命名這兩個類

    object Ex extends App {
    
      import java.util.{HashMap => JavaHashMap}
      import scala.collection.mutable.{HashMap => ScalaHashMap}
    
      var map1 = new JavaHashMap[Int, String]()
      map1.put(1, "cris")
      map1.put(2, "james")
      map1.put(3, "simida")
    
    
      var map2 = new ScalaHashMap[Int, String]()
      for (key <- map1.keySet().toArray()) { // key 的數據類型是 AnyRef
        // asInstanceOf 強制數據類型轉換
        map2 += (key.asInstanceOf[Int] -> map1.get(key))
      }
      println(map2.mkString("||")) // 2 -> james||1 -> cris||3 -> simida
    
    }
    

抽象

我們在前面去定義一個類時候,實際上就是把一類事物的共有的屬性和行為提取出來,形成一個物理模型(模板)。這種研究問題的方法稱為抽象

示例代碼

object Demo extends App {

  var account = new Account("招行:888888", 200, "123456")
  account.query("123456")

  account.save("123456", 100)
  account.query("123456")

  account.withdraw("123456", 250)
  account.query("123456")

}

class Account(val no: String, var balance: Double, var pwd: String) {
  def query(pwd: String): Unit = {
    if (pwd != this.pwd) {
      println("密碼錯誤!")
    } else {
      println(s"卡號:${this.no},餘額還有:${this.balance}")
    }
  }

  def save(pwd: String, money: Double): Unit = {
    if (pwd != this.pwd) {
      println("密碼錯誤")
    } else {
      this.balance += money
      println(s"卡號:${this.no},存入:${money},餘額為:${this.balance}")
    }
  }

  def withdraw(pwd: String, money: Double): Unit = {
    if (pwd != this.pwd) {
      println("密碼錯誤")
    } else if (money > this.balance) {
      println("餘額不足")
    } else {
      this.balance -= money
      println(s"卡號:${this.no},取出:${money},餘額為:${this.balance}")
    }
  }
}

Cris的Scala筆記整理(七):面向對象

相關文章

Cris的Scala筆記整理(十):隱式轉換

Cris的Scala筆記整理(九):面向對象高級

Cris的Scala筆記整理(八):面向對象中級繼承和多態

Cris的Scala筆記整理(八):面向對象中級封裝