【Scala】Scala之Methods


一、前言

  前面學習了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中方法的一些使用技巧,謝謝各位園友的觀看~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM