【Scala】Scala之Methods

1、前言java

  前面學習了Scala的Class,下面接着學習Method(方法)。編程

2、Methodapp

  Scala的方法與Java的方法相似,都是添加至類中的行爲,可是在具體的實現細節上差別很大,下面展現一個參數爲整形,返回值爲String的方法定義 ide

// java
public String doSomething(int x) {
    // code here
}

// scala
def doSomething(x: Int): String = {
    // code here
}

  Scala中的方法能夠寫的更爲簡潔,以下函數

def plusOne(i: Int) = i + 1

  將參數值加1後返回,不用顯示定義返回值類型,除了已經給出的差異,還有其餘差異以下學習

    · 指定方法訪問控制(可見性)測試

    · 爲方法參數設置默認值的能力ui

    · 調用方法時指定方法參數名稱的能力this

    · 聲明方法拋出的異常spa

    · 在方法中使用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變量來存儲方法返回值,而後經過._一、._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,這使得流式風格在子類中也可使用

3、總結

  本篇主要講解了Scala中方法的一些使用技巧,謝謝各位園友的觀看~

相關文章
相關標籤/搜索