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建構函式分為:主建構函式 + 輔助建構函式。一個主建構函式是以下的組合:
- 建構函式引數
- 在類內部被調的方法
- 類內部執行的語句和表示式
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() }
解釋:
- Scala主建構函式相當模糊,Person類的整個都是主建構函式部分
- Person類的建構函式中兩個引數firstName、lastName被定義為var欄位,scala會預設為其新增get/set方法。而屬性HOME則沒有,private val相當於private final,不可以被其他物件直接訪問
- Scala中描述符: private val,相當於Java中的private final
- Scala中預設新增的get/set方法,比如上訴程式碼中age屬性,會新增方法名叫age() ----- getter方法,age_$eq(int age) ----- setter方法
4.2 控制建構函式欄位的可見性(var/val/private)
Scala中建構函式引數可見性,使用var/val/private關鍵字修飾所控制。可通過下列口訣記住:
- 如果一個欄位被宣告為var,Scala會為該欄位生成getter和setter方法
- 如果欄位是val,Scala只會生成getter方法
- 如果一個欄位沒有var或val修飾符,Scala比較保守,不會生成getter方法和setter方法
- 欄位有使用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為名的方法定義輔助建構函式。通過不同的引數列表,定義多個輔助建構函式。提醒:每個建構函式必須呼叫之前已經定義定義好的建構函式,也就是說,任何輔助建構函式都會呼叫主建構函式
- 輔助建構函式必須使用this命名建立
- 每個輔助建構函式必須呼叫之前定義的建構函式開始
- 每個建構函式必須有不同的簽名(引數列表)
程式碼:
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方法
- 如果是class,欄位沒有使用var/val修飾,則不會生成getter和setter方法。如果是case class,欄位沒有使用var/val修飾,預設為val,生成getter方法
- 使用private或private[this]訪問修飾符定義欄位
private 和 private[this]對比
- 使用private修飾字段,則Scala不會自動生成getter和setter方法。
- 定義一個private[this]欄位讓私有化更進一步,讓欄位物件私有化
- 如下程式碼,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欄位型別
- 非String和數字型別的欄位,使用Option和Some
- String型別欄位,預設為空串。var str : String = ""
- 數值型別 ----- 給定合適的型別和預設值。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中的特質比抽象類更靈活。基於下面兩個原因使用抽象類:
- 需要建立一個有建構函式引數的基類,特質(trait)不允許有建構函式引數
- 需要被Java呼叫
程式碼
abstract class BaseController {
def save(): Unit = {
// save action
}
def update() : Unit = {
// update action
}
// abstract method
def connect()
}
4.12.1 Scala中類和物件的總結
- object ----- 伴生物件,類似於Java中的靜態類
- class ----- 基本的類,與Java中類似
- abstract classs ----- 抽象類,關於何時使用抽象類,參見4.12
- case class ----- case類,比較特殊,適用於模式匹配場景使用。與普通的class有如下不同:預設val和var修飾符,屬性的可見性不同;
- trait ----- 特質,類似於Java1.8中介面。可以有屬性,方法,抽象方法
- 繼承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,取決於具體的需求
- 抽象的var生成getter和setter方法
- 抽象的val生成getter方法
- 在一個抽象類(或特質)中定義一個抽象欄位時,Scala編譯器不會在結果程式碼中建立這個欄位,只會生成該var或val欄位的響應方法
- override關鍵字不是必須的
- 開發人員可以在抽象類中定義了一個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類會自動生成模版程式碼的諸多好處:
- 自動生成一個apply方法,這樣就不用new關鍵字建立新的例項
- Case類的建構函式引數預設是val,自動生成getter方法
- 自動生成一個toString方法
- 自動生成一個unapply方法,在模式匹配是很好用
- 自動生成equals方法和hashCode方法
- 自動生成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類中內部類從屬於外部類)
- 外部類class ----- 內部類class
- 外部類class ----- 內部物件object
- 內部物件object ----- 外部類class
- 內部物件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)
}