1. 程式人生 > >Scala實戰專欄 (四) ——— 類和屬性

Scala實戰專欄 (四) ——— 類和屬性

scala-basic 基礎篇

@author 魯偉林
Scala基礎知識介紹,主要包括《Scala程式設計實戰》的基礎章節

GitHub地址: https://github.com/thinkingfioa/scala-in-action
本人部落格地址: http://blog.csdn.net/thinking_fioa/article/details/78265745

4. 類和屬性

與Java相比,Scala在類的宣告、構造和欄位的訪問控制存在很大的差異。Java更囉嗦,Scala更簡潔。第4章專案原始碼閱讀

4.1 建立一個主建構函式

Scala建構函式分為:主建構函式 + 輔助建構函式。一個主建構函式是以下的組合:

  1. 建構函式引數
  2. 在類內部被調的方法
  3. 類內部執行的語句和表示式
class Person(var firstName : String, var lastName : String) {
  println("the constructor begins")

  // some class field
  private val HOME = System.getProperty("user.name")
  var age = 0

  override def toString: String = s"$firstName $lastName is $age years old"

  def printHome(): Unit = {
    println(s"HOME = $HOME")
  }

  printHome()
}

解釋:

  1. Scala主建構函式相當模糊,Person類的整個都是主建構函式部分
  2. Person類的建構函式中兩個引數firstName、lastName被定義為var欄位,scala會預設為其新增get/set方法。而屬性HOME則沒有,private val相當於private final,不可以被其他物件直接訪問
  3. Scala中描述符: private val,相當於Java中的private final
  4. Scala中預設新增的get/set方法,比如上訴程式碼中age屬性,會新增方法名叫age() ----- getter方法,age_$eq(int age) ----- setter方法

4.2 控制建構函式欄位的可見性(var/val/private)

Scala中建構函式引數可見性,使用var/val/private關鍵字修飾所控制。可通過下列口訣記住:

  1. 如果一個欄位被宣告為var,Scala會為該欄位生成getter和setter方法
  2. 如果欄位是val,Scala只會生成getter方法
  3. 如果一個欄位沒有var或val修飾符,Scala比較保守,不會生成getter方法和setter方法
  4. 欄位有使用var或val修飾符,且同時使用private關鍵字修飾,也不會生成getter方法和setter方法

4.2.1 Case類

case類中建構函式引數的生成方式與其他的類略有不同。Case類的建構函式引數預設是val,生成getter方法,其他類預設不會生成getter方法和setter方法

程式碼:

class OtherPerson(name :String) {

}

case class CasePerson(name : String) {

}

object Class4P2 {
  val otherPerson : OtherPerson = new OtherPerson("fioa")
  val casePerson : CasePerson = CasePerson("ppp")

//  println(s"otherPerson ${otherPerson.name}") 報錯
  println(s"casePerson ${casePerson.name}")
}

4.3 定義輔助建構函式

定義多個輔助建構函式,方便使用者通過不同的方式建立物件例項。在類內部以this為名的方法定義輔助建構函式。通過不同的引數列表,定義多個輔助建構函式。提醒:每個建構函式必須呼叫之前已經定義定義好的建構函式,也就是說,任何輔助建構函式都會呼叫主建構函式

  1. 輔助建構函式必須使用this命名建立
  2. 每個輔助建構函式必須呼叫之前定義的建構函式開始
  3. 每個建構函式必須有不同的簽名(引數列表)

程式碼:

class Pizza(var crustSize : Int, var crustType : String) {

  def this(crustSize: Int) {
    this(crustSize, Pizza.DEFAULT_CRUST_TYPE)
  }

  override def toString: String = s"Thinking eat Pizza $crustSize, $crustType"
}

object Pizza {
  val DEFAULT_CRUST_SIZE = 12
  val DEFAULT_CRUST_TYPE = "THIN"
}

4.3.1 為case類生成輔助建構函式

Case類是一個會自動生成很多模版程式碼的特殊類。使用類的伴生物件中的apply方法,來為Class類新增一個輔助建構函式

程式碼

case class Person4P2(var name : String, var age : Int)

object Person4P2 {
  def apply(name : String) = new Person4P2(name, 0)
}

object Class4P3 {
  var p : Person4P2 = Person4P2("thinking")
}

4.4 定義私有的主建構函式 ----- 單例模式需要

為了使用單例,需要建立一個私有主建構函式,使用private關鍵字。Scala中使用單例,需要用到伴生物件

程式碼

class Brain private (var name : String, var speed : Long) {

  override def toString: String = s"$name have $speed ms"

  def printBrain() : Unit = {
    print(toString())
  }
}

object Brain {
  val brain = new Brain("thinking", 1)
  def getIntance: Brain = brain
}

object Class4P4 {
  def main(args: Array[String]): Unit = {
    Brain.getIntance.printBrain()
  }
}

伴生物件 ----- 討論

簡單來說,一個伴生物件就是定義在與類的同一個檔案中,同時物件和類有相同的名字。如上object Brain是類Brain的伴生物件。伴生物件類中的方法都是該物件的靜態方法,類似於Java中的靜態類

4.5 設定建構函式引數的預設值

給建構函式引數提供一個預設值,在呼叫建構函式時可以指定或者也可以不指定引數。 eg: class Socket(val timeout : Int = 1000)

程式碼

class Socket (val timeout : Int = 1000) {

}

object Class4P5 {

  def main(args: Array[String]): Unit = {
    val socket : Socket = new Socket(timeout = 3000)
  }
}

4.6 覆寫預設的訪問和修改方法

覆寫Scala自動生成的getter或者setter。Scala中這個優點麻煩,推薦下列方法來實現覆寫預設的方法

class Stock(var _symbol : String) {
  def symbol() : String = _symbol

  def symbol_ (s :String) : Unit =  {
    this._symbol = symbol
  }
}

4.7 阻止生成getter和setter方法

Scala中如果某個變數定義為var,將會自動生成getter和setter方法;如果變數定義成val,將會自動生成getter方法

  1. 如果是class,欄位沒有使用var/val修飾,則不會生成getter和setter方法。如果是case class,欄位沒有使用var/val修飾,預設為val,生成getter方法
  2. 使用private或private[this]訪問修飾符定義欄位

private 和 private[this]對比

  1. 使用private修飾字段,則Scala不會自動生成getter和setter方法。
  2. 定義一個private[this]欄位讓私有化更進一步,讓欄位物件私有化
  3. 如下程式碼,price欄位無法被相同型別的其他例項訪問

程式碼

class Book {
  private[this] var price : Double = _

  def setPrice(price:Double ): Unit = {
    this.price = price
  }

  def isHigher(that : Book): Boolean = {
    // 報錯,無法在this物件中訪問that物件的price
//    this.price > that.price
    true
  }
}

4.8 將程式碼塊或者函式賦給欄位

用程式碼塊或者呼叫函式給類裡面的欄位初始化賦值,如果演算法要求較長的執行時間,可加上lazy關鍵字

程式碼

class FooTest {

  var text = {
    var lineText="unkonw text"
    try {
      lineText = "try code"
    } catch {
      case e : Exception => lineText = "catch code"
    }
    lineText
  }
}

4.9 設定未初始化的var欄位型別

在一個類中設定一個未初始化的var欄位型別

  1. 非String和數字型別的欄位,使用Option和Some
  2. String型別欄位,預設為空串。var str : String = ""
  3. 數值型別 ----- 給定合適的型別和預設值。varB : Byte = 0; varC : Char = 0; varl : Long = 0;

程式碼

case class Person4P9(var userName : String, var passwd : String) {
  var age : Int = 0
  var address: Option[Address] = None : Option[Address]
}

case class Address(var city : String, var state : String) {
}

object Class4P9 {
  def main(args: Array[String]): Unit = {
    var person : Person4P9 = Person4P9("thinking", "fioa")
    person.address = Some(Address("beijin", "nanjinRoad"))

    person.address.foreach { a => {
        println(a.city)
        println(a.state)
      }
    }
  }
}

4.10 在繼承類時處理建構函式引數 ----- 繼承必看

繼承一個基類,需要處理定義在基類中建構函式的引數,同時也需要處理子類中的新引數。

通過將基類的建構函式引數定義為var或val。當定義在子類建構函式時,不要用var或val宣告類間的公用的欄位; 在子類中使用var或者val欄位定義新的建構函式

程式碼

class Person4P11(var name : String , var age : Int) {
  override def toString: String = s"name is $name, age is $age"
}

class Employee(name : String, age : Int, var address : Address) extends Person4P11(name, age){
  override def toString: String = s"name is $name, age is $age, address is $address"
}

object Class4P10 {
  def main(args: Array[String]): Unit = {
    val employee : Employee  = new Employee("ppp", 26, Address("Anhui", "anqin"))
    println(employee)
  }
}

4.11 呼叫父類的建構函式

當子類建構函式需要呼叫超類建構函式,這個Scala語言和Java語言有較大的不同。

Scala中只有子類的主建構函式才有資格呼叫超類的建構函式,子類的輔建構函式第一行必須是呼叫當前類的另一個建構函式,所以子類的輔建構函式不能直接呼叫超類的任何一個建構函式

4.12 何時使用抽象類

Scala中的特質比抽象類更靈活。基於下面兩個原因使用抽象類:

  1. 需要建立一個有建構函式引數的基類,特質(trait)不允許有建構函式引數
  2. 需要被Java呼叫

程式碼

abstract class BaseController {
  def save(): Unit = {
    // save action
  }

  def update() : Unit = {
    // update action
  }

  // abstract method
  def connect()
}

4.12.1 Scala中類和物件的總結

  1. object ----- 伴生物件,類似於Java中的靜態類
  2. class ----- 基本的類,與Java中類似
  3. abstract classs ----- 抽象類,關於何時使用抽象類,參見4.12
  4. case class ----- case類,比較特殊,適用於模式匹配場景使用。與普通的class有如下不同:預設val和var修飾符,屬性的可見性不同; 
  5. trait ----- 特質,類似於Java1.8中介面。可以有屬性,方法,抽象方法
  6. 繼承Enumeration ----- 列舉類,程式碼參見如下

程式碼

object HttpMethod extends Enumeration {
  type HttpMethod = Value
  val UNKNOWN = Value("UNKNOWN")
  val GET = Value("GET")
  val OCCUR = Value("OCCUR")
  val POST = Value("POST")
}

4.13 在抽象基類(或者特質)定義屬性

在一個抽象類(或特質)中宣告var 或者 val欄位,這些欄位可以是抽象的,也可以是具體的實現。在一個抽象類(或特質)裡把抽象欄位宣告成var 或 val,取決於具體的需求

  1. 抽象的var生成getter和setter方法
  2. 抽象的val生成getter方法
  3. 在一個抽象類(或特質)中定義一個抽象欄位時,Scala編譯器不會在結果程式碼中建立這個欄位,只會生成該var或val欄位的響應方法
  4. override關鍵字不是必須的
  5. 開發人員可以在抽象類中定義了一個def欄位,而不是val也是可以的

程式碼

abstract class Animal4P13 {
  var greeting : String = "Hello"
  val age : Int = 0

  val sayHello: Unit = {println(s"Animal4P13, say $greeting")}
}

class Dog4P13 extends Animal4P13 {
  greeting = "Dog"

  override val sayHello: Unit = {println(s"Dog4P13, say $greeting")}
}

object Class4P13 {

  def main(args: Array[String]): Unit = {
    val dog : Dog4P13 = new Dog4P13()
  }
}

4.14 用Case類生成模版程式碼

Scala生成模版程式碼,模版程式碼包括了訪問和修改方法,apply,unapply,toString,equals,和hashCode等方法。通過將類定義為Case類會自動生成模版程式碼的諸多好處:

  1. 自動生成一個apply方法,這樣就不用new關鍵字建立新的例項
  2. Case類的建構函式引數預設是val,自動生成getter方法
  3. 自動生成一個toString方法
  4. 自動生成一個unapply方法,在模式匹配是很好用
  5. 自動生成equals方法和hashCode方法
  6. 自動生成copy方法

4.14.1 提醒

Case類主要是為了建立"不可變的記錄",非常適用於模式匹配場景使用。建議Case類的引數預設是val,不允許寫成var,否則就違背了"不可變的記錄"的本意

4.15 定義一個equals方法(物件的相等性)

Scala中的物件相等性判斷和Java具有較大不同點。眾所周知,Java語言中==方法是比較兩個物件的引用的相等性;而在Scala中==方法等同於equals方法

程式碼

class Person4P14(val name : String, val age : Int) {

  def canEqual(a : Any) : Boolean = a.isInstanceOf[Person4P14]

  override def equals(that : Any) : Boolean = that match {
    case that : Person4P14 => this.canEqual(that) && this.hashCode() == that.hashCode()
    case _ => false
  }

  override def hashCode() : Int = {
    // TODO 補充hashCode值
    31
  }
}

4.16 建立內部類

Scala內部類從屬於外部類物件的,有以下四種內部類的表現形式。(java類中內部類從屬於外部類)

  1. 外部類class ----- 內部類class
  2. 外部類class ----- 內部物件object
  3. 內部物件object ----- 外部類class
  4. 內部物件object ----- 內部物件object

程式碼

class OuterClass {
  class InnerClass {
    var x = 1
  }

  object InnerObject {
    val y = 2
  }
}

object OuterObject {
  class InnerClass {
    var m =3
  }

  object InnerObject {
    var n = 4
  }
}

object Class4P16 {

  //呼叫外部類中的內部類
  val oc = new OuterClass
  var ic = new oc.InnerClass
  println(ic.x)

  //呼叫外部類中的內部類物件
  println(new OuterClass().InnerObject.y)
  
  //呼叫外部物件的內部類
  println((new OuterObject.InnerClass).m)

  //呼叫外部物件的內部物件
  println(OuterObject.InnerObject.n)
}