1. 程式人生 > >Scala - 06 - 面向物件

Scala - 06 - 面向物件

1- 類

1.1- 簡介:類、方法及物件

類是用來建立物件的藍圖。 Scala檔案中包含的多個類之間,都是彼此可見的,不需要宣告為public。 建立物件 定義好類以後,就可以使用new關鍵字來建立物件。
  • 欄位預設為public,在類外部和內部都可以訪問該欄位。
  • 私有欄位使用private關鍵字修飾,外界無法訪問,只有在類內部可以訪問該欄位。
方法
  • 通過def關鍵字實現方法的定義
  • 方法的返回值:方法裡面的最後一個表示式的值就是方法的返回值,不需要靠return語句。
  • 如果方法不返回任何值,返回值的型別就為Unit。
  • 如果包含方法具體操作語句的大括號裡面只有一行語句,可以省略大括號。
package testscala

object TestScala {
  def main(args: Array[String]) {
    println("Testing, Scala!")

    val myTest1 = new myCounter
    myTest1.increment()
    // myTest1.increment  // 呼叫無參方法時,可以省略方法名後面的圓括號
    println(myTest1.current)

    val myTest2 = new myCounter2
    myTest2.increment(5)
    println(myTest2.current)
  }

}

class myCounter {
  private var value = 0
  def increment(): Unit = { value += 1 }
  // def increment(): Unit = value += 1  // 大括號裡面只有一行語句,可以省略大括號
  def current(): Int = { value }
}

class myCounter2 {
  private var value = 0
  def increment(step: Int): Unit = { value += step }
  def current(): Int = { value }
}

1.2- 定義類似getter和setter的方法

Scala沒有提供getter和setter方法,但可以通過定義類似getter和setter的方法,來訪問私有value欄位。
package testscala

object TestScala {
  def main(args: Array[String]) {
    println("Testing, Scala!")

    val myTest3 = new myCounter3
    println(myTest3.value)
    myTest3.value = 3
    println(myTest3.value)
    myTest3.increment(1)
    println(myTest3.current)

    val myTest4 = new myCounter4
    println(myTest4.value)
    myTest4.value = 3
    println(myTest4.value)
    myTest4.increment(1)
    println(myTest4.current)
  }
}

class myCounter3 {
  var value = 0 // 變數預設為public,對外部可見
  def increment(step: Int): Unit = { value += step }
  def current(): Int = { value }
}

class myCounter4 {
  private var privateValue = 0 // 變數為private,私有欄位
  def value = privateValue
  def value_=(newValue: Int) {
    if (newValue > 0) privateValue = newValue // 新值為正數才可以修改
  }
  def increment(step: Int): Unit = { value += step }
  def current(): Int = { value }
}

1.3- 構造器

Scala構造器包含1個主構造器和若干個(0個或多個)輔助構造器。 主構造器 Scala的每個類都有主構造器。 Scala的主構造器是整個類體,需要在類名稱後面羅列出構造器所需的所有引數,這些引數被編譯成欄位,欄位的值就是建立物件時傳入的引數的值。
package helloscala

object helloscala {
  def main(args: Array[String]) {
    println("Testing, Scala!")

    val myTest5 = new myCounter5("Timer", 2)
    myTest5.info
    myTest5.increment(1)
    printf("Current Value is: %d\n", myTest5.current)
  }
}

class myCounter5(val name: String, val mode: Int) {
  private var value = 0
  def increment(step: Int): Unit = { value += step }
  def current(): Int = { value }
  def info(): Unit = { printf("Name:%s and mode is %d \n", name, mode) }
}
輔助構造器 輔助構造器的名稱為this。 每個輔助構造器都必須呼叫一個此前已經定義的輔助構造器或主構造器。
package helloscala

object helloscala {
  def main(args: Array[String]) {
    println("Testing, Scala!")

    val myTest6 = new myCounter6
    myTest6.info
    myTest6.increment(1)
    printf("Current Value is: %d\n", myTest6.current)

    val myTest62 = new myCounter6("Runner")
    myTest62.info
    myTest62.increment(2)
    printf("Current Value is: %d\n", myTest62.current)

    val myTest63 = new myCounter6("Timer", 2)
    myTest63.info
    myTest63.increment(3)
    printf("Current Value is: %d\n", myTest63.current)
  }
}

class myCounter6 {
  private var value = 0
  private var name = ""
  private var mode = 1
  def this(name: String) {
    this()
    this.name = name
  }
  def this(name: String, mode: Int) {
    this(name)
    this.mode = mode
  }
  def increment(step: Int): Unit = { value += step }
  def current(): Int = { value }
  def info(): Unit = { printf("Name:%s and mode is %d \n", name, mode) }
}

2- 面向物件-物件

2.1- 單例物件

Scala並沒有提供Java那樣的靜態方法或靜態欄位。 但可以用object關鍵字定義單例物件,具備和Java靜態方法同樣的功能。
package helloscala

object helloscala {
  def main(args: Array[String]) {
    println("Testing, Scala!")
    printf("The first person id is %d.\n", Person.newPersonId())
    printf("The second person id is %d.\n", Person.newPersonId())
    printf("The third person id is %d.\n", Person.newPersonId())
  }
}

object Person {
  private var lastId = 0
  def newPersonId() = {
    lastId += 1
    lastId
  }
}

2.2- 伴生物件

在Scala中通過伴生物件來實現同時包含例項方法和靜態方法的類。 當單例物件與某個類具有相同的名稱時,它被稱為這個類的“伴生物件”。 類和它的伴生物件必須存在於同一個檔案中,而且可以相互訪問私有成員(欄位和方法)。 Scala原始碼經過編譯後都會變成JVM位元組碼,class和object在Java層面都會被合二為一。class裡面的成員成了例項成員,object成員成了static成員。 伴生類中的成員和伴生物件中的成員都被合併到一起,並且伴生物件中的方法成為靜態方法。
package testscala

object TestScala { // 單例物件與某個類具有相同的名稱,稱為這個類的伴生物件

  private var lastId = 0
  private def newPersonId() = {
    lastId += 1
    lastId
  }

  def main(args: Array[String]) {
    println("Testing, Scala!")
    val person1 = new TestScala("AAA")
    val person2 = new TestScala("BBB")
    person1.info()
    person2.info()
  }
}

class TestScala {
  private val id = TestScala.newPersonId() //呼叫伴生物件中的方法
  private var name = ""

  def this(name: String) {
    this()
    this.name = name
  }

  def info() { printf("The id of %s is %d.\n", name, id) }
}
 注意: 可以通過javap命令對.class檔案進行反編譯,來確認伴生類中的成員和伴生物件中的成員都被合併到一起,並且,伴生物件中的方法成為靜態方法。 另外,進行反編譯時,要把伴生物件中方法的private修飾符去掉。如果不去掉,作為伴生物件的私有方法,在javap反編譯後,在執行結果中是看不到這個方法的。

2.3- 應用程式物件

每個Scala應用程式都必須從一個物件的main方法開始。 第一種方法:直接使用scala命令執行得到結果。 程式碼中沒有定義類,就是一個單例物件,因此,可以不用編譯,直接使用scala命令執行得到結果。
[email protected] MINGW64 /d/Anliven-Running/Zen/ScalaProjets/temptest
$ ls -l
total 1
-rw-r--r-- 1 guowli 1049089 88 Nov 21 11:31 test.scala

[email protected] MINGW64 /d/Anliven-Running/Zen/ScalaProjets/temptest
$ cat test.scala
object HelloWorld {
  def main(args: Array[String]){
    println("Hello, World!")
  }
}

[email protected] MINGW64 /d/Anliven-Running/Zen/ScalaProjets/temptest
$ scala test.scala
Hello, World!

[email protected] MINGW64 /d/Anliven-Running/Zen/ScalaProjets/temptest
$ ls -l
total 1
-rw-r--r-- 1 guowli 1049089 88 Nov 21 11:31 test.scala

[email protected] MINGW64 /d/Anliven-Running/Zen/ScalaProjets/temptest
$

第二種方法:先編譯再執行

首先使用scalac編譯命令對.scala檔案進行編譯,然後使用scala命令執行。
[email protected] MINGW64 /d/Anliven-Running/Zen/ScalaProjets/temptest
$ scalac test.scala

[email protected] MINGW64 /d/Anliven-Running/Zen/ScalaProjets/temptest
$ ls -l
total 3
-rw-r--r-- 1 guowli 1049089 665 Nov 21 11:33 'HelloWorld$.class'
-rw-r--r-- 1 guowli 1049089 602 Nov 21 11:33 HelloWorld.class
-rw-r--r-- 1 guowli 1049089  88 Nov 21 11:31 test.scala

[email protected] MINGW64 /d/Anliven-Running/Zen/ScalaProjets/temptest
$ scala -classpath . HelloWorld
Hello, World!

[email protected] MINGW64 /d/Anliven-Running/Zen/ScalaProjets/temptest
$

2.4- apply方法和update方法

apply方法 用括號傳遞給變數(物件)一個或多個引數時,Scala 會把它轉換成對 apply方法的呼叫 類中定義apply方法:
package testscala

object TestScala {
  def main(args: Array[String]) {
    println("Testing, Scala!")
    val myObject = new TestApplyClass
    println(myObject("param1")) // 執行myObject("param1")時呼叫了apply方法
  }
}

class TestApplyClass {
  def apply(param: String): String = {
    println("apply method called, parameter is: " + param)
    "Hello World!" // apply方法的返回值
  }
}

單例物件中定義apply方法:

package testscala

object TestScala {

  def main(args: Array[String]) {
    println("Testing, Scala!")
    val group = TestScala("AAA", "BBB")  // 呼叫了apply方法,獲得返回值並賦值給變數
    println(group)
  }

  def apply(param1: String, param2: String): String = {
    println("apply method called")
    param1 + " and " + param2  // 方法的返回值
  }

}

伴生類和伴生物件中的apply方法:

package testscala

object TestScala {
  def main(args: Array[String]) {
    val a = ApplyTest() // 呼叫伴生物件中的apply方法並返回該方法呼叫的值      
    a.greetingOfClass
    a() // 呼叫伴生類中的apply方法         
  }
}

object ApplyTest {
  def apply() = {
    println("apply method in object is called")
    new ApplyTest()  // 返回ApplyTest類的例項化物件
  }
}

class TestScala {
}

class ApplyTest {
  def apply() = println("apply method in class is called!")
  def greetingOfClass: Unit = {
    println("Greeting method in class is called.")
  }
}

示例:apply方法

 val myStrArr = Array("AAA", "BBB", "CCC")       //> myStrArr  : Array[String] = Array(AAA, BBB, CCC)
利用Scala中Array物件已定義的apply方法可以直接給物件傳遞引數。 也就是呼叫Array類的伴生物件Array的apply方法,完成陣列的初始化。 理解:將伴生物件作為工廠使用,就使用關鍵字new建立例項化物件
package testscala

object TestScala {
  def main(args: Array[String]) {
    val mycar = Car("BMW") // 呼叫伴生物件中的apply方法建立一個Car類的例項化物件
    mycar.info()
  }
}

class Car(name: String) {
  def info() { println("Car name is " + name) }
}

object Car {
  def apply(name: String) = new Car(name) // 呼叫伴生類Car的構造方法建立一個Car類的例項化物件
}
update方法 當對帶有括號幷包括一到若干引數的物件進行賦值時,編譯器將 呼叫物件的update方法,把括號裡的引數和等號右邊的物件一起作為update方法的輸入引數來執行呼叫。
  val myStrArr = new Array[String](3)             //> myStrArr  : Array[String] = Array(null, null, null)
  myStrArr(0) = "AAA" //實際呼叫了伴生類Array中的update方法,執行myStrArr.update(0,"AAA")
  myStrArr(1) = "BBB" //實際呼叫了伴生類Array中的update方法,執行myStrArr.update(1,"BBB")
  myStrArr.update(2, "CCC") // 執行myStrArr.update(2,"CCC")
  for (x <- myStrArr) println(x)                  //> AAA
                                                  //| BBB
                                                  //| CCC

注意:在進行元組賦值的時候,之所以沒有采用Java中的方括號myStrArr[0],而是採用圓括號的形式,myStrArr(0),是因為存在上述的update方法的機制。

3- 面向物件-繼承

Scala和Java一樣,不允許類從多個超類繼承。 Scala中的繼承與Java有著顯著的不同:
  1. 重寫一個非抽象方法必須使用override修飾符。
  2. 只有主構造器可以呼叫超類的主構造器。
  3. 在子類中重寫超類的抽象方法時,不需要使用override關鍵字。
  4. 可以重寫超類中的欄位。
抽象類
  • 使用關鍵字abstract定義抽象類
  • 抽象類不能直接被例項化,可以被其他類繼承
  • 定義抽象類中的抽象方法不需要使用abstract關鍵字,方法體為空
  • 抽象類中定義的欄位必須宣告型別,否則編譯會報錯;如果欄位沒有初始化值,就表示是一個抽象欄位
擴充套件類   擴充套件類可以直接被例項化。
package testscala

object TestScala {
  def main(args: Array[String]) {
    val myCar1 = new BMWCar()
    val myCar2 = new BenzCar()
    myCar1.greeting()
    myCar1.info()
    myCar2.greeting()
    myCar2.info()
  }
}

abstract class Car { //抽象類
  val carBrand: String //抽象欄位沒有初始化值
  def info() //抽象方法,不需要使用abstract關鍵字,方法體為空
  def greeting() { println("Welcome to my car!") }  //非抽象方法,方法體內容非空
}

class BMWCar extends Car {  //擴充套件類
  override val carBrand = "BMW" //重寫超類欄位,需要使用override關鍵字,否則編譯會報錯
  def info() { printf("This is a %s car.\n", carBrand) } //重寫超類的抽象方法時,可以不使用override關鍵字
  override def greeting() { println("Welcome to my BMW car!") } //重寫超類的非抽象方法,必須使用override關鍵字
}

class BenzCar extends Car { //擴充套件類
  override val carBrand = "Benz"
  def info() { printf("This is a %s car.\n", carBrand) }
  override def greeting() { println("Welcome to my Benz car!") }
}

4- 面向物件-特質

Scala中沒有介面的概念,而是提供了“特質(trait)”,不僅實現了介面的功能,還具備了很多其他的特性。 Scala中一個類只能繼承自一個超類,卻可以實現多個特質,通過重用特質中的方法和欄位可以實現多重繼承。
  • 使用關鍵字trait定義特質
  • 特質中沒有方法體的方法,預設為抽象方法。抽象方法不需要使用abstract關鍵字。
  • 特質定義完成後,可以使用extends或with關鍵字把特質混入類中。
  • 特質可以包含具體實現,特質中的欄位和方法不一定要是抽象的。
  • 如果特質只包含了抽象欄位和抽象方法,相當於實現了類似Java介面的功能。
package helloscala

object helloscala {
  def main(args: Array[String]) {
    println("Testing, Scala!")
    val myCarId1 = new BenzCarId()
    val myCarId2 = new BMWCarId()
    printf("My first CarId is %d.\n", myCarId1.currentId)
    myCarId2.greeting("Welcome my second car.")
    printf("My second CarId is %d.\n", myCarId2.currentId)
  }
}

trait CarId { //使用關鍵字trait定義特質
  var id: Int //定義抽象欄位
  def currentId(): Int //定義抽象方法,不需要使用abstract關鍵字
}

trait CarGreeting { //使用關鍵字trait定義特質 
  def greeting(msg: String) { println(msg) } //定義非抽象方法,
}

class BenzCarId extends CarId { //使用extends關鍵字把特質混入類中
  override var id = 10000 //BYD汽車編號從10000開始
  def currentId(): Int = { id += 1; id } //返回汽車編號
}

class BMWCarId extends CarId with CarGreeting { //使用extends關鍵字混入第1個特質,後面可以反覆使用with關鍵字混入更多特質
  override var id = 20000
  def currentId(): Int = { id += 1; id }
}