一、前言
前面學習了Scala的Class,下面接着學習Method(方法)。
二、Method
Scala的方法與Java的方法類似,都是添加至類中的行為,但是在具體的實現細節上差異很大,下面展示一個參數為整形,返回值為String的方法定義
// java public String doSomething(int x) { // code here } // scala def doSomething(x: Int): String = { // code here }
Scala中的方法可以寫的更為簡潔,如下
def plusOne(i: Int) = i + 1
將參數值加1后返回,不用顯示定義返回值類型,除了已經給出的差別,還有其他差別如下
· 指定方法訪問控制(可見性)
· 為方法參數設置默認值的能力
· 調用方法時指定方法參數名稱的能力
· 聲明方法拋出的異常
· 在方法中使用var類型的字段
2.1 控制方法的范圍
1. 問題描述
Scala的方法默認為public的,你需要像Java一樣控制方法的范圍
2. 解決方案
Scala提供了如下范圍控制選項
· 對象私有范圍的
· 私有的
· 受保護的
· 包級別的
· 指定包級別的
· 公有的
最嚴格的訪問就是將方法定義為對象私有的,其只能在當前對象上被調用,其他同類型對象無法訪問,在方法前面添加private[this]即可
private[this] def isFoo = true
在如下示例中,doFoo的參數為Foo實例,但是isFoo被定義為對象私有方法,如下代碼編譯不成功
這是因為other無法訪問isFoo方法,該方法被定義為只有this(當前對象)可以訪問
可以將方法定義為private,這樣當前對象可以訪問,並且同類型的其他對象也可以訪問
當定義為private時,子類無法訪問,如下示例編譯不會通過
將heartBeat定義為protected之后則可通過編譯
Scala中的protected含義與Java不太相同,其在同一個包的其他類無法訪問該protected方法,如下示例
package world { class Animal { protected def breathe {} } class Jungle { val a = new Animal a.breathe // error: this line won't compile } }
編譯時報錯如下
表示Jungle類無法訪問同包下的Animal類的方法
為了是方法對於同包下的其他類可見,可以使用private[packageName]來修飾方法
package model { class Foo { private[model] def doX {} private def doY {} } class Bar { val f = new Foo f.doX // compiles f.doY // won't compile } }
運行結果如下
除了讓方法在同包下可見,Scala還可以讓包在類繼承結構中的不同層級下可見
package com.hust.grid.model { class Foo { private[model] def doX {} private[grid] def doY {} private[hust] def doZ {} } } import com.hust.grid.model._ package com.hust.grid.view { class Bar { val f = new Foo f.doX // won't compile f.doY f.doZ } } package com.hust.common { class Bar { val f = new Foo f.doX // won't compile f.doY // won't compile f.doZ } }
其中doX在model包下其他類中可見,doY在grid包下其他類中可見,doZ在hust包下其他類中可見
如果方法沒有修飾符,其就是public的,下面示例中任何類都可以調用doX方法
package com.hust.grid.model { class Foo { def doX {} } } package org.xyz.bar { class Bar { val f = new com.hust.grid.model.Foo f.doX } }
3. 討論
Scala中的訪問控制符比Java的要稍微復雜點,需要不斷體會和使用
2.2 調用超類的方法
1. 問題描述
為了保持代碼的簡潔性,你需要調用父類定義的方法
2. 解決方案
在基礎使用上,Scala與Java一樣,使用super指代超類,然后使用方法名指代具體方法
class WelcomeActivity extends Activity { override def onCreate(bundle: Bundle) { super.onCreate(bundle) // more code here ... } }
如果類繼承了多個traits,並且這些traits實現了相同的方法,這時你不僅可以選擇方法,並且還可以選擇哪個traits
trait Human { def hello = "the Human trait" } trait Mother extends Human { override def hello = "Mother" } trait Father extends Human { override def hello = "Father" }
你可以使用如下不同的方法調用hello方法
class Child extends Human with Mother with Father { def printSuper = super.hello def printMother = super[Mother].hello def printFather = super[Father].hello def printHuman = super[Human].hello }
可以進行如下測試
object Test extends App { val c = new Child println(s"c.printSuper = ${c.printSuper}") println(s"c.printMother = ${c.printMother}") println(s"c.printFather = ${c.printFather}") println(s"c.printHuman = ${c.printHuman}") }
運行結果如下
c.printSuper = Father c.printMother = Mother c.printFather = Father c.printHuman = the Human trait
當類繼承多traits並且有多個相同方法時,可以使用super[traitName].methodName 來調用不同父類中的相同方法
值得注意的是,其無法使用到父類的繼承結構(如父類的父類),其只能調用其直接繼承的父類的方法,如下例所示,super[Animal].walk編譯報錯
trait Animal { def walk { println("Animal is walking") } } class FourLeggedAnimal extends Animal { override def walk { println("I'm walking on all fours") } } class Dog extends FourLeggedAnimal { def walkThenRun { super.walk // works super[FourLeggedAnimal].walk // works super[Animal].walk // error: won't compile } }
由於Dog並為直接繼承Animal,因此不能使用super[Animal].walk來調用Animal的方法
2.3 設置方法參數的默認值
1. 問題描述
你想要定義方法參數值的默認值,以便在調用方法時不用顯示定義該方法參數
2. 解決方案
在方法簽名中指定默認值,如下所示
class Connection { def makeConnection(timeout: Int = 5000, protocol: = "http") { println("timeout = %d, protocol = %s".format(timeout, protocol)) // more code here } }
timeout的默認值為5000,protocal的默認值為"http",可以通過如下方法調用該方法
c.makeConnection() c.makeConnection(2000) c.makeConnection(3000, "https")
如果你喜歡指定參數名時,可以使用如下方式
c.makeConnection(timeout=10000) c.makeConnection(protocol="https") c.makeConnection(timeout=10000, protocol="https")
3. 討論
如同構造函數參數一樣,你可以為方法參數設置默認值,方法中參數的賦值從左到右,使用如下方法可以使用timeout和protocal的默認值
c.makeConnection()
可以設置timeout值為2000,而protocal使用默認值
c.makeConnection(2000)
可以設置同時設置timeout和protocal的值
c.makeConnection(2000, "https")
但是通過上述方法無法單獨設置protocal的值,可以這樣單獨設置protocal的值,而timeout默認為5000
c.makeConnection(protocol="https")
當你的方法中既有默認值也有非默認值,你需要將由默認值的參數放在最后
可以上述調用都會報錯,重新調整參數位置即可
2.4 調用方法時使用參數名
1. 問題描述
當調用方法時,你更喜歡使用參數名
2. 解決方案
常用的指定參數來調用方法格式如下
methodName(param1=value1, param2=value2, ...)
有如下類
class Pizza { var crustSize = 12 var crustType = "Thin" def update(crustSize: Int, crustType: String) { this.crustSize = crustSize this.crustType = crustType } override def toString = { "A %d inch %s crust pizza.".format(crustSize, crustType) } }
可以使用如下方法調用
p.update(crustSize = 16, crustType = "Thick")
p.update(crustType = "Pan", crustSize = 14)
3. 討論
使用參數名調用方法顯得冗長,但是具有更好的可讀性,當參數類型相同時,其顯得尤為有用,可比較如下兩種調用
engage(true, true, true, false)
engage(speedIsSet = true, directionIsSet = true, picardSaidMakeItSo = true, turnedOffParkingBrake = false)
顯然第二種具有更好的可讀性
2.5 定義返回多項(元組)的方法
1. 問題描述
你想要從一個方法中返回多個值,但是你不想將這些值封裝在類中
2. 解決方案
Scala可以使用tuple,讓你從方法中返回多個值,如下返回三元組
def getStockInfo = { // other code here ... ("NFLX", 100.00, 101.00) // this is a Tuple3 }
然后調用該方法並將返回值賦值給變量
val (symbol, currentPrice, bidPrice) = getStockInfo
3. 討論
在Java中也可以從方法中返回多個值,但是其是將多個值封裝在類中而並非使用tuple
getStockInfo方法返回的是三元組tuple3,tuple最多可以容納22個變量,即22元組tuple22,如下定義的是二元組
當然也可以直接使用一個val變量來存儲方法返回值,然后通過._1、._2符號來訪問對應的值
2.6 強制調用者不使用括號調用方法
1. 問題描述
你想強調如下編程風格,在調用訪問方法時不使用括號
2. 解決方案
在定義訪問方法名后不使用括號,如下例所示
class Pizza { // no parentheses after crustSize def crustSize = 12 }
此時,調用者調用方法時不能使用括號,否則會報錯
3. 討論
在調用訪問方法時不適用括號並沒有任何副作用,並且可以強制調用者不適用括號進行調用
2.7 創建接受可變參數的方法
1. 問題描述
為了使方法更為靈活,你想要定義一個接受可變參數的方法
2. 解決方案
在參數字段類型后面添加*即可表示方法接受可變個參數
def printAll(strings: String*) {
strings.foreach(println)
}
針對以上方法,可以有如下調用
printAll() printAll("foo") printAll("foo", "bar") printAll("foo", "bar", "baz")
可以使用_*操作符來適應序列(Array、List、Seq、Vector等),所以其也可被用作實參調用方法
// a sequence of strings val fruits = List("apple", "banana", "cherry") // pass the sequence to the varargs field printAll(fruits: _*)
3. 討論
當定義一個包含可變參數的方法時,其必須要放在方法簽名的最后,在可變參數后面定義一個參數是非法的
並且一個方法中只能有一個可變參數,當一個參數為可變參數,調用時你不需要提供任何參數,如下
當定義一個接受可變個整形的參數的方法時,當使用(a)參數調用(b)無參數調用,其結果不相同
當使用一個或多個參數調用時,其是第一個種情況,當不使用參數調用時,其是第二種情況,是空列表,這樣可以防止拋出異常
2.8 定義可拋出異常的方法
1. 問題描述
你想要定義一個可以拋出異常的方法,這樣可以警告調用者,或者是你的代碼將會被Java代碼調用
2. 解決方案
使用@throws注釋來聲明可能拋出的異常,將注釋放在方法之前
@throws(classOf[Exception]) override def play { // exception throwing code here ... }
當一個方法可能拋出多個異常時,將其一一列出
@throws(classOf[IOException]) @throws(classOf[LineUnavailableException]) @throws(classOf[UnsupportedAudioFileException]) def playSoundFileWithJavaAudio { // exception throwing code here ... }
3. 討論
Scala使用注釋聲明異常與Java的以下處理方式相同
public void play() throws FooException { // code here ... }
Scala的受檢查異常與Java的不相同,Scala不需要方法聲明會拋出的異常,並且不需要調用者捕捉異常
即便Scala不需要異常時受檢查的,如果不進行測試,也會出現Java中的結果,在下面的代碼中,第二個println永遠不會被調用
object BoomTest extends App { def boom { throw new Exception } println("Before boom") boom // this line is never reached println("After boom") }
2.9 支持流式編程風格
1. 問題描述
你想要創建一個調用可以使用流式風格編程的API,其也被稱為方法鏈
2. 解決方案
流式編程風格可以讓你的方法調用聚合在一起,如下所示
person.setFirstName("LEE") .setLastName("SF") .setAge(25) .setCity("WH") .setState("HB")
為了支持這種風格,做法如下
· 如果類可以被繼承,可將方法的返回類型定義為this.type
· 如果類不能被繼承,可將方法的返回類型定義為this
下面示例展示了this.type作為set*方法的返回類型
class Person { protected var fname = "" protected var lname = "" def setFirstName(firstName: String): this.type = { fname = firstName this } def setLastName(lastName: String): this.type = { lname = lastName this } } class Employee extends Person { protected var role = "" def setRole(role: String): this.type = { this.role = role this } override def toString = { "%s, %s, %s".format(fname, lname, role) } }
使用如下
object Main extends App { val employee = new Employee // use the fluent methods employee.setFirstName("Al") .setLastName("Alexander") .setRole("Developer") println(employee) }
3. 討論
如果確定類不會被繼承,則將返回類型定義為this.type毫無意義,可以直接返回this即可,如下例所示
final class Pizza { import scala.collection.mutable.ArrayBuffer private val toppings = ArrayBuffer[String]() private var crustSize = 0 private var crustType = "" def addTopping(topping: String) = { toppings += topping this } def setCrustSize(crustSize: Int) = { this.crustSize = crustSize this } def setCrustType(crustType: String) = { this.crustType = crustType this } def print() { println(s"crust size: $crustSize") println(s"crust type: $crustType") println(s"toppings: $toppings") } }
使用如下
object FluentPizzaTest extends App { val p = new Pizza p.setCrustSize(14) .setCrustType("thin") .addTopping("cheese") .addTopping("green olives") .print() }
當類能夠被繼承時,則需要將返回類型定義為this.type,這使得流式風格在子類中也可以使用
三、總結
本篇主要講解了Scala中方法的一些使用技巧,謝謝各位園友的觀看~