scala函數式編程


 函數式編程

靜態關鍵字不是面向對象中的語法,scala中函數可理解為java中的靜態方法

 scala是完全面向函數的編程語言,scala中的類其實也是一個函數

這里的函數類似於java的靜態方法,體現的是功能的封裝

java中方法的聲明

            public static void(返回值類型) 方法名 (參數列表) throws 異常列表{
                方法體
                return "123"
            }

           java中方法的調用
           new User().test()

           scala中函數的聲明:
           scala中沒有public的概念,也沒有靜態的概念,所以為了和屬性區分開,使用def關鍵字聲明
           scala中返回值類型需要放置在方法名的后面,使用冒號分隔
           scala中參數列表的聲明和java一致,但參數聲明有變化: 參數名:參數類型。...
           scala中沒有throws關鍵字,所以函數根本就不需要拋出異常
           scala中函數也可以返回結果


        // scala中最基本的函數聲明
        def  test( name : String, age : Int ) : String = {
            // 方法體
            println("name = " + name + ", age = " + age)
            return "123"
        }

        // 調用函數
        test("zhangsan", 12)

聲明

def main(args: Array[String]): Unit = {

    def hello(name: String): Unit = {
      println(s"Hello $name")
    }

    hello("smile")

    //1.無參無返回值加Unit
    def f1(): Unit = {
      println("無參無返回值")
    }

    //f1()
    //2.有1個參,無返回值
    def f2(name: String): Unit = {
      println("有1個參,無返回值")
    }

    //f2("kris")
    //3.多個參,無返回值
    def f3(name: String, age: Int): Unit = {
      println("多個參,無返回值")
    }

    //f3("kris", 22)
    //4.無參有返回值 聲明一個變量接收函數返回值或者直接打印
    def f4(): String = {
      return "abc"
    }

    val result: String = f4()
    println(result)
    //println(f4())
    
    //5.有參,有返回值
    def f5(name: String): String = {
      return name
    }
    println(f5("kris"))


  }
View Code

 

 def fun1(): String = {
    return "smile"
  }
  //println(fun1())
  // 方法中如果使用了return關鍵字,那么方法一定要有返回值類型
  // 如果不使用return關鍵字,那么返回值類型可以省略
  // 方法可以自動推斷方法返回值類型,根據方法體邏輯中的最后一行代碼的結果來推斷,所以可以把return省略掉
  def fun2() = {
    "alex"
  }
  //println(fun2())

  def fun3() : String = {
    "kris"
  }
  //println(fun3())
  ////////////////////////////////return//////////////////////////////////////
  // 函數如果明確了返回值為Unit,那么方法體中如果使用return不起作用
  def fun4(): Unit = {
    return "abc"
  }
  //println(fun4())   //()

  //如果明確方法沒有返回值,也不希望自動推斷,那么可以省略 = 等號
  def fun5(){
    "哈哈"
  }
  //println(fun5()) //()
  // 如果函數沒有參數列表,那么參數小括號可以省略,並且調用時,不需要使用小括號
  def fun6{
    println("省略參數列表")
  }
  //fun6
  //println(fun6)
  // 如果方法體的邏輯代碼只有一行,那么花括號可以省略,為了區分方法名和方法體,需要使用等號連接
  def fun7 = println("Hi")
  //fun7
  //println(fun7)
  // 之所以使用def關鍵字來聲明函數,就是為了在某些場合和變量區分開
  def  fun8 = "kris"
  println(fun8)

  // 匿名函數
  ()->{}

 

  // 在參數類型的后面增加星號,表示可變參數
  // 可變參數必須放置在參數列表的后面
  def func1(age: Int, name : String* ): Unit ={
    println(age + ",\t" + name)
  }
    //func1(22, "alex", " smile", "kris") //22,    WrappedArray(alex,  smile, kris)

  // 給參數默認值,如果調用方法時,沒有傳遞指定的參數,那么會采用默認值代替
  // 如果參數了指定的參數,那么會將默認值覆蓋掉
  def func2(name : String = "kk", age: Int): Unit ={
    println(s"$name, $age")
  }
  func2("kris", 22) //kris, 22

  def func3(name : String, age : Int): Unit ={
    println(s"$name, $age")
  } // 方法在調用時,傳遞的參數從左到右依次進行賦值
  // 帶名參數
  func3(age = 21, name = "kk")

函數當成參數--即高階函數

(參數類型)=>(返回值類型)

    // 將函數當成參數
    def func1(i: Int): Int ={
       20 + i
    }
    def func2(i: Int): Unit ={
      11 + i
    }
    def func3(i: Int): Unit ={
      println("i:" + i)
    }
    // 將符合要求的函數(輸入Int,輸出Int)作為參數傳遞給另外一個函數
    def func01(f: (Int) => Int): Unit ={
      println(f(3))
    }
    func01(func1)//23

柯里化  底層就是閉包

柯里化(Currying)是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受余下的參數且返回結果的新函數的技術

def test( a : Int, b:Int, c:Int )--->> def test( a:Int )(b:Int)(c:Int)

實現原理其實就是將每一個需要多個條件(參數)的復雜邏輯函數進行拆分,轉換為單一條件(參數)的簡單函數,通過簡單函數的組合解決復雜的邏輯操作,類似於JavaEE中三層架構的感覺

def test( a:Int )((Int, Int)=>Int)(b:Int)

在柯里化的實現中,使用了閉包的功能,所謂的閉包就是將函數外的局部變量包含到函數內部的一種處理機制,這樣可以避免函數外的變量被回收導致程序異常的情況。

柯里化

  def func3(i: Int): Unit ={
        println("i:" + i)
      } 
  def func02() = {
        func3 _ //函數當成返回值,把方法返回而不是結果返回,加_
      }
    func02()(4) //i:4

  // 函數柯里化(主要目的是完成隱式轉換);上面方式有點亂,聲明的時候可以直接加兩個括號!跟上邊的方式是一樣的;這種簡化就是柯里化,把復雜的業務邏輯拆成一段一段,它不是一個函數;
  def func03()(i: Int): Unit ={
    println(i)
  }
  func03()(4) //4

閉包
    // 改變局部變量的生命周期,將變量包含到當前函數的內部,形成了一個閉包的效果;執行完f4,它的局部變量i就無效了(彈棧了),f5把它包進去
    // scala中函數可以聲明在任意的地方
    def func04(i: Int)={
      def func05(j: Int): Unit ={
        println(i + j)
      }
      func05 _
    }
    func04(10)(20) //30

    def func06(i: Int)(j: Int): Unit ={ //按柯里化方式聲明,柯里化底層就是閉包
      println(i + j)
    }
    //func06(7)(3)

匿名函數

(參數)=>{方法體--把邏輯代碼傳進來了}
參數只用一次可省略加個_即可(作為占位符)

    def f1(f:(String) => Unit): Unit ={
      f("kris")
    }
    def f2(s: String): Unit ={
      println("Hello " + s)
    }
    //f1(f2)  這種方式還要聲明一個函數,太麻煩了,使用匿名函數
    // 匿名函數  沒有名稱也不用聲明; 參數=>方法體;可以傳一段邏輯代碼,擴展性強   遵循了OCP原則,擴展性開放
    f1((x) => {println("Hello" + x)})  //Hello kris


    f1(println(_)) //kris 占位符 //f1((x) => {println(x)}) 進一步的省略,這兩種寫法是等同的

 柯里化練習| 把函數作為參數傳進來

Operator(1)(++)(1) -->> 2

def main(args: Array[String]): Unit = {
    def Operator(i: Int) = {                 //Operator應該返回一個函數;柯里化也就是閉包的概念,把一個完整的進行拆分一個個單一的函數;Operator並沒有執行,最終的執行結果應該靠++決定,但需要用它把它返回即可
      def Middle(f:(Int, Int)=>Int) = {     //第二個括號即Middle,效果是1+1的結果打印出來,應該把一個算法傳進來;
        def Inner(j: Int) ={                //第三個括號
          f(i, j) //執行操作,傳進兩個參數
        }
        Inner _
      }
      Middle _
    }
    def ++(i: Int, j: Int): Int = {  //這里執行具體功能,++
      i / j        //加減乘除都可以;邏輯可以傳進來,但主題架構沒有變;
    }
    println(Operator(1)(++)(1)) //1
  }

傳函數還要自己聲明---->再簡化,使用匿名函數

  //val i: Int = Operator(1)((x, y) => (x + y))(10)
    val i: Int = Operator(1)(_+_)(10)  //參數只用了一次還可以省略
    println(i) //11

 

  //sum(3)(3)(println(_))-->> 6
  def sum(i: Int) = {
    def middle(j: Int)={
      def inner(f: (Int, Int) => Unit) = {
        f(i, j)
      }
      inner _
    }
    middle _
  }

  sum(3)(3)((x: Int, y: Int)=>{println(x+y)}) //6
  //sum(3)(3)((x, y) => (println(x + y)))

遞歸

遞歸有可能棧溢出;(不是內存溢出),棧內存溢出---跟線程有關;壓棧; 棧幀(即每塊內存)

    // 遞歸
    // 1) 函數自己調用自己
    // 2) 函數中一定要有跳出的邏輯
    // 3) 函數自己調用自己時傳遞的參數應該有規律
    // 4)scala中遞歸函數需要明確返回值類型
    def test(num: Int): Int ={
    if (num == 1){
      1
    }else{
      num*test(num-1)
      }
    }
    println(test(5)) //120

 

惰性函數

JIT及時編譯器,可打亂程序執行順序
內存加載順序是由jvm決定,怎么快怎么來
windows、linux是線程優先級,蘋果系統是分時

看一個應用場景

惰性計算(盡可能延遲表達式求值)是許多函數式編程語言的特性。惰性集合在需要時提供其元素,無需預先計算它們,這帶來了一些好處。

首先,您可以將耗時的計算推遲到絕對需要的時候。其次,您可以創造無限個集合,只要它們繼續收到請求,就會繼續提供元素。

函數的惰性使用讓您能夠得到更高效的代碼。Java 並沒有為惰性提供原生支持,Scala提供了。

Java實現懶加載的代碼,比如常用的單例模式懶漢式實現時就使用了上面類似的思路實現

當函數返回值被聲明為lazy時,函數的執行將被推遲,直到我們首次對此取值,該函數才會執行。這種函數我們稱之為惰性函數,在Java的某些框架代碼中稱之為懶加載(延遲加載)。
  def main(args: Array[String]): Unit = {
    lazy val res = sum(10, 20)
    println("########################")
    println("res=" + res)
  }
  def sum(n1: Int, n2: Int): Int ={
    println("sum() 執行了..")
    return n1 + n2
  }

/*
不加lazy
  sum() 執行了..
  ########################
  res=30
加lazy
  ########################
  sum() 執行了..
  res=30
*/

注意事項和細節

1)lazy 不能修飾 var 類型的變量

2)不但是 在調用函數時,加了 lazy ,會導致函數的執行被推遲,我們在聲明一個變量時,如果給聲明了 lazy ,那么變量值得分配也會推遲。 比如 lazy val i = 10

過程

函數沒返回值就是過程--就是一段邏輯

偏(一部分)函數(partial function) 也叫模式匹配

map函數的作用是將集合中所有的數據進行邏輯操作,數據總量不變。map不支持偏函數,collect支持;

偏函數會導致不符合條件的數據被過濾掉,所以數據總量發生變化;過濾filter、groupBy

① 在對符合某個條件,而不是所有情況進行邏輯操作時,使用偏函數是一個不錯的選擇

② 將包在大括號內的一組case語句封裝為函數,我們稱之為偏函數,它只對會作用於指定類型的參數或指定范圍值的參數實施計算,超出范圍的值會忽略.

③ 偏函數在Scala中是一個特質PartialFunction

偏函數簡化形式

聲明偏函數,需要重寫特質中的方法,有的時候會略顯麻煩,而Scala其實提供了簡單的方法

    def f2: PartialFunction[Any, Int] = {
      case i: Int => i + 1  // case語句可以自動轉換為偏函數
    }
  val list: List[Any] = List(1, 2, 3, 4, 5, "abc").collect(f2)
    println(list) 

進一步的簡化:

    val list2 = List(1, 2, 3, 4, 5, "abc").collect { //偏函數,只對其中的一部分起作用
      case i: Int => i + 2  //List(3, 4, 5, 6, 7) //再進一步的簡化:不用聲明函數直接寫個case即可
    }
    println(list2) //List(3, 4, 5, 6, 7

扁平化栗子:

    val list = List(1,2,List(3,4), "abc")
    println(list.flatMap(x => List(x))) //List(1, 2, List(3, 4), abc) 沒有真正的扁平化,1,2不能迭代; 區別對待,偏函數

    println(list.flatMap{ //
      //case x: Int => List(x)
      case y: Iterable[Int] => y  //List(1, 2, 3, 4, abc),case順序如果調換下就是List(1, 2, List(3, 4), abc)這個結果了
      case a => List(a)
    })

再比如:

    val map = Map("a"->1, "b"->2)
    map.foreach{
      case(k, v) => {
        println(k + "=>" + v)
      }
    }
===>
a => 1
b => 2

 

控制抽象

將一段代碼(從形式上看),作為參數傳遞給高階函數,在高階函數內部執行這段代碼. 其使用的形式如 breakable{} 。

滿足如下條件:
① 參數是函數
② 函數參數沒有輸入值也沒有返回值

可以將一段代碼邏輯作為參數傳遞給函數(方法),有利於功能的擴展

    Breaks.breakable {
      val list = List(1, 2, 3, 4)
      for (i <- list) {
        if (i == 3) {
          Breaks.break()
        }
      }
    }

模仿源碼:

class Breaks() extends scala.AnyRef {
  def breakable(op : => scala.Unit) : scala.Unit = { /* compiled code */ }

把一段邏輯寫進來就可以寫{ } 花括號了;

    test{
      val list = List(1, 2, 3, 4)
      for (i <- list) {
        println(i)
      }
    }
    def test(f: => Unit)={
      f  //f()函數可以調用,scala中要求f加括號了才能加,沒有加()也不能加
    }
for (){

} //for如果不是關鍵字, for()()柯里化,把一段邏輯寫進來就可以寫{ } 花括號了;

 

 異常

Scala提供try和catch塊來處理異常。try塊用於包含可能出錯的代碼。catch塊用於處理try塊中發生的異常。可以根據需要在程序中有任意數量的try...catch塊。
語法處理上和Java類似,但是又不盡相同

Scala異常處理

ctrl+alt+t

/*    Java異常處理回顧
    try { // 可疑代碼
      val i = 0
      val b = 10
      val c = b / i // 執行代碼時,會拋出ArithmeticException異常
    } catch {
      case e: Exception =>
        e.printStackTrace()
    } finally {
      // 最終要執行的代碼
      System.out.println("java finally")
    }*/

try { val r = 10 / 0 } catch { case ex: ArithmeticException => println("捕獲了除數為零的算術異常") case ex: Exception => println("捕獲了異常") } finally { // 最終要執行的代碼 println("scala finally") }

  1)我們將可疑代碼封裝在try塊中。 在try塊之后使用了一個catch處理程序來捕獲異常。如果發生任何異常,catch處理程序將處理它,程序將不會異常終止。
  2)Scala的異常的工作機制和Java一樣,但是Scala沒有“checked(編譯期或受檢)”異常,即Scala沒有編譯異常這個概念,異常都是在運行的時候捕獲處理。
  3)用throw關鍵字,拋出一個異常對象。所有異常都是Throwable的子類型。throw表達式是有類型的,就是Nothing,因為Nothing是所有類型的子類型,所以throw表達式可以用在需要類型的地方
  4)在Scala里,借用了模式匹配的思想來做異常的匹配,因此,在catch的代碼里,是一系列case子句來匹配異常。當匹配上后 => 有多條語句可以換行寫,類似 java 的 switch case x: 代碼塊..

  5)異常捕捉的機制與其他語言中一樣,如果有異常發生,catch子句是按次序捕捉的。因此,在catch子句中,越具體的異常越要靠前,越普遍的異常越靠后,如果把越普遍的異常寫在前,把具體的異常寫在后,在scala中也不會報錯,但這樣是非常不好的編程風格。
  6)finally子句用於執行不管是正常處理還是有異常發生時都需要執行的步驟,一般用於對象的清理工作,這點和Java一樣。

  7)Scala提供了throws關鍵字來聲明異常。可以使用方法定義聲明異常。 它向調用者函數提供了此方法可能引發此異常的信息。 它有助於調用函數處理並將該代碼包含在try-catch塊中,以避免程序異常終止。在scala中,可以使用throws注釋來聲明異常

 


免責聲明!

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



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