二, Scala 函数式编程详解
五, 函数式编程
- 面向对象编程(OOP)
- 面向对象思想: 使用对象来映射现实中的事物, 使用对象的关系来描述事事物之间的联系
- 面向对象编程: 把要解决的问题按照一定的规则划分为多个独立的对象, 这些对象拥有各自的属性和方法, 通过调用对象的方法来解决问题;
[举个栗子]
用户通过JDBC连接和读取数据库
- 对象: 用户;
- 属性: 用户名, 密码;
- 行为(方法): 登录, 连接JDBC, 读取数据库;
对象的本质: 对数据和行为的一个封装
- 函数式编程(FP)
- 面向过程编程: 分析解决问题所需要的步骤, 然后用函数把这些步骤一一实现;
[举个栗子]
用户通过JDBC连接和读取数据库
- 请求->用户名,密码->连接JDBC->读取数据库
函数的本质: 函数可以当做一个值进行传递
5.1 函数基础
- Scala 既是一个完全面向对象编程语言, 万物皆对象, 不像Java那样是半吊子OO(基本数据类型经过包装才能作为对象);
- Scala 又是一个完全函数式的编程语言, 万物皆为函数;
5.1.1 函数基本语法

5.1.2 函数和方法的区别
- 概念: 
  - 函数: 未完成某一个功能的程序语句的集合
- 方法: 类中的函数称之为方法
 
- Scala中的函数: 
  - Scala语言可以在任何的语法结构中声明任何的语法;
- 函数没有重载和重写的概念, 而方法可以进行重载, 重写;
- Scala 中函数可以嵌套定义;
 

5.1.3 函数的参数
- 可变参数; def function(args : String*)
- 参数默认值; def function(args : String = “default args”)
- 带名参数; function(agrs = “参数值”) 调用方法时使用
Object functionTest{
  //可变参数, 会在参数类型后加一个*标记, 如果函数有多个参数时, 要把可变参数放在参数列表的后面
  def funcOne(name : String, age : Int, subJect : String): Unit = {
    println("Hello, I am ${name}, ${age} years old, i have some examnation test today, like ${subJect}")
  }
  
  def funcTwo(name: String, age : Int = 18): Unit = {
    println(s"$name, $age");
  }
  //如果参数传递了值, 会把默认值替换掉, 如果不要替换, 可以直接省略这个字段的传值
funcTwo("李四")
}
5.1.4 函数至简原则
| 情形 | 简化 | 
|---|---|
| 1. 标准写法 | 按部就班, 但是注意 return是可以省略的 | 
| 2. 函数只有一行代码 | {}可省略 | 
| 3. 返回值类型可推断 | :返回值类型可省略, 方法中return语句时, 不可省略! | 
| 4. 函数返回空(Unit) | return 关键字失效 | 
| 5. 返回值类型可推断 + 函数返回为空 | : 返回值而类型 =直接省略 | 
| 6. 函数没有参数, 但是有参数列表小括号 | 调用此函数, 括号可有可无 | 
| 6.1函数没有参数, 参数列表小括号已经省略 | 调用此函数时小括号也必须要省略! | 
| (重要!)7. 如果不关心函数名称, 只关心逻辑处理 | def 函数名,= 返回值类型全部省略. (参数列表) => {函数体} | 
(参数列表) => {函数体}, 又叫匿名函数, 或者是 lamda表达式, 对, 就是等同于Java8的那个, 只不过写法稍微有点差别: Java8中的lamda表达式为 (参数列表) -> {函数体}
[举个栗子: ]
object TestSimplify {
  def main(args: Array[String]): Unit = {
    // 1. 函数标准写法
    // 在Scala中, return是冗余的, Scala会把带返回值的函数的最后一行直接返回
    def f1(name: String): String ={
      //return name + "你好"
      name + "你好"
    }
    println(f1("lisi"))
    //2. 函数体只有一行代码, 可省略花括号
    def f2(name: String): String = return name + "你好"
    println(f2("zhangsan"))
    //3.返回值类型如果可推断, 可以省略 ':返回值类型',
    // 但是, 注意噢, 如果方法中有return语句时是不可省略返回值类型的
    def f3(name: String)= println(name + ", 你好")
    f3("wangwu")
    //4. 函数声明返回为空(Unit)时, return 关键字失效
    //5.Scala 如果期望是无返回值类型,可以省略等号
    def f5(name: String){
      println(s"hello, ${name}")
    }
    f5("liuqi")
    //6. 函数没有参数, 但是有参数列表小括号, 调用此函数时, 括号可加可不加
    //6. 函数没有参数, 参数列表小括号已经省略, 调用此函数时小括号也必须要省略!
    def f6(): Unit = {
      println("hello, f6")
    }
    //
    f6
    f6()
    def f6_1: Unit = {
      println("hello, f6_1")
    }
    f6_1
    
    //7. 如果不关心函数名称, 只关心逻辑处理, 那么 'def 函数名' 可以省略掉
    //也叫做是匿名函数/lamda表达式
    //7.1 普通方法的书写
    def f7_1(name: String): Unit = {
      println("hello, " + name)
    }
    //省略 `def f7` 和  `: 返回值类型 = `
        // 格式:  (参数列表) => {方法体}
    (name: String) => {
      println("hello, " + name)
    }
      //那么. 如何调用这个匿名函数呢?==> 让他有名字
      var f7_2 = (name: String) =>{
        println("hello, motherfucker" + name)
      }
      f7_2("sww")
  }
  
}
5.2 函数高级
5.2.1 匿名函数
5.2.1.1 概念和使用
- 简单再说下what is 匿名函数?
- 如果对一个函数我们不关心函数名称和返回值, 只去聚焦于函数的实现逻辑, 那么我们可以使用匿名函数
- 写法: (参数列表) => {函数体}
- 在对匿名函数化简举栗子之前, 我们先预习下函数的高级用法之一: 把一个函数作为另一个函数的参数, 即函数作为参数进行传递:
    //匿名函数
    // 匿名函数的函数名称. String => Unit  (参数类型) => 返回值类型
    val hiFunc: (String, Int) => Unit = (name: String, age: Int) => {
      println("hello, my name is " + name)
    }
    //把匿名函数作为参数
    // 函数作为另一个函数的参数,写法:   函数名: (参数列表) => 返回值类型
    def Self_Introduction(hiFunction : (String, Int) => Unit): Unit = {
      hiFunction("李四", 25) // 形参噢
    }
    Self_Introduction(hiFunc) //实参
从上面的栗子可以得出两点:
- 匿名函数的函数名写法: var 匿名函数名称 : (参数列表) => 返回值类型 , 如: var f1: (name: String, age: Int) => Unit, 有这么个匿名函数, 返回值为空, 参数列表包括String类型的name和Int类型的age , 我们让它的函数名为f,
- 把匿名函数作为另一个函数的参数: def f2(匿名或普通函数的函数名 : (参数类型 => 返回值类型)) , 如, def f2(f1: (name: String, age: Int) => Unit, addr: String)
5.2.1.2 匿名函数简化规则
匿名函数至简原则:
- 参数类型可以省略, 会根据形参进行自动推导;
- 类型省略后, 只有一个参数的, 可以省略小括号; 没有参数和参数超过1的. 不能省略圆括号;
- 匿名函数只有一行, 则大括号也可省略;
- 参数只出现一次, 则参数省略且后面的参数可用
_代替;
在举栗子之前, 先明确一个知识点:
如何定义一个变量, 这个变量的类型是一个参数类型为(name: String, age: Int), 返回值为空的函数?
答案就是: var f: (String, Int) => Unit = 匿名函数/普通函数
[举个栗子]
object TestAnonymousSimplify {
    def main(args:  Array[String]): Unit = {
       //1. 匿名函数标准写法
      //标准匿名函数: (参数列表) => {方法体}
                    // 这个匿名函数的函数名我们设置为helloFunc
      var helloFunc: String => Unit = (name: String) => {
        println("hello, " + name)
      }
      //1. 未化简的普通函数f0(匿名函数helloFunc作为参数传递进去)
      //标准的把函数作为另一个函数的参数写法:   函数名: 参数类型 => 返回值类型
        def f0(helloFunction: String => Unit, name: String): Unit={
          helloFunc(name)
        }
     //1.1 未化简的匿名函数f1(匿名函数helloFunc作为参数传递进去)
         // 注意这里 f1 : (String => Unit, String) => Unit, :后面的都是为了描述f1这个函数的,
         // 这句话表明, f1这个函数的参数列表是两个参数,
         // 第一个参数是一个函数, 这个函数有一个参数, 类型为String, 返回值为空;
         // 第二个参数的参数类型为String
        // 所以定义一个函数变量, 就像是定义常见变量一样, 比如 name: String, 就是表示了一个String类型的变量name
      var f1: (String=> Unit, String) => Unit = (helloFunction: String => Unit, name: String) =>{
          helloFunction(name)
      }
      f1(helloFunc, "我是f1")
      化简的几种方法
        //2. 参数类型可省略. 因为会根据实参(传入的参数)进行自动推导
        // f2
      var f2: (String => Unit, String) => Unit = (helloFunction, name) => {
          helloFunction(name)
        }
        f2(helloFunc, "我是f2")
        //3. 参数类型省略后, 一个参数的可以省略小括号, 没有参数或参数超过1个, 不能省略圆括号
          //本例子, 只对f3传入一个函数作为参数, name就不传入了
        var f3: (String => Unit) => Unit = helloFunc => {
          helloFunc("我是f3")
        }
        f3(helloFunc)
      //4. 参数只出现一次, 则参数省略且后面的参数可用`_`代替
        var f4: (String => Unit) => Unit = (_) =>{
          helloFunc("我是f4")
        }
       f4(helloFunc)
    }
}
这里我们总结一下前面用到的各种花里胡哨的写法, 包括匿名函数的定义, 函数类型的参数, 把函数作为参数传给另一个函数:
| 写法 | 解释 | 
|---|---|
| (name:String, age: Int) => {函数体} | 匿名函数写法就是()=>{}, (参数列表)=>{函数体} | 
| var f = (String, Int) => Unit = {函数体} | 函数类型的参数写法就是 (参数列表中的数据类型) => 返回值 | 
| var f1 = ((f的参数类型)=> f1的返回值, String, name) => String | 函数作为函数的参数写法就是 对函数类型参数写法的套娃 | 
5.2.2 函数的高阶用法
对于一个函数, 我们可以: 定义函数, 调用函数
Obeject TestFuction{
  def main(args: Array[String]): Unit = {
    //调用函数
    foo()
  }
  //定义函数
  def foo(): Unit = {
    println("foo...")
  }
}
但是, 函数还有更高阶的用法: 包括作为值进行传递, 作为参数进行传递, 作为函数返回值返回;
5.2.2.1 函数作为值进行传递
- 在Scala中, 直接对函数进行输出时, 都会有相应的值, 当函数有返回值时, 输出的是返回值, 当函数返回值为Unit(空)时, 输出的是一对括号 ();
- 所以我们可以把函数作为值进行传递, 直接传递给某一个变量, 输出的是函数的返回值;
- 我们也可以直接把函数整体的赋值给某个变量, 从而使这个变量跟原始函数一样使用;
object TestValTrans {
  def main(args: Array[String]): Unit = {
    def fool(name: String, age: Int): Int = {
      println(s"my name is ${name}, i am ${age} years old.")
      8
    }
    //(1)调用 fool 函数,把返回值给变量 f
    // fool没有参数时, 可写为: var fun = fool
    var fun = fool("zhangssan", 13)
    //(2)在被调用函数 foo 后面加上 _,相当于把函数 foo 当成一个整体, 传递给变量 f1
    var fun_all = fool _
    println(fool("wangwu", 28))
    println(fun_all("wangwu", 28))
    //(3)如果明确变量类型,那么不使用下划线也可以将函数作为整体传递给 变量
    var fun_all_1:(String, Int) => Int = fool
    print(fun_all_1("刘2", 46))
  }
}
5.2.2.2 函数作为参数进行传递
- 函数作为参数: (函数参数1的数据类型, 参数2的数据类型…) => 函数返回值类型
object TestFunc {
   def main(args: Array[String]): Unit = {
     //1.函数普通写法
     def hello(name: String, age: Int): Unit = {
       println(s"my name is ${name}, i am ${age} years old.")
     }
     //2. 匿名函数写法
//     (name: String, age: Int) => {
//       println(s"my name is ${name}, i am ${age} years old.")
//     }
     //3. 把前面的函数1作为参数传递给下面的函数
     def func( helloFunction: (String, Int) => Unit, name1: String, age1: Int): Unit ={
       helloFunction(name1, age1) //helloFuntion是形参噢
     }
                 
     func(hello, "lisi", 28) //调用函数, 传入实参, hello函数就是实参
   }
}
5.2.2.3 函数作为函数返回值返回
def main(args: Array[String]): Unit = {
  def f1() = {
    def f2() = {
    }
    f2 _
  }
  val f = f1()
// 因为 f1 函数的返回值依然为函数,所以可以变量 f 可以作为函数继续调用
  f()
// 上面的代码可以简化为
  f1()()
来两个小练习:
object AnonymousPractice {
  def main(args: Array[String]): Unit = {
    //1.
    var f0: (Int, String, Char) => Boolean = (id: Int, name: String, ch: Char) =>{
      if(id.equals(0) && name.equals("") && ch.equals('0'))
        false
      else
        true
      }
    f1
  }
   //2.1 对上面第二道题答案的简化( 匿名函数)
   def func(x: Int): String => (Char => Boolean) = {
      (y: String) => {
        (z: Char) => {
            if(x == 0 && y == "" && z == '0')
                false
            else
                true
        }
      }
    }
  //2.2 继续简化
}
5.2.2.4 高阶函数使用案例
待补充
5.2.3 函数柯里化&闭包
闭包: 如果一个函数, 访问到了它的外部(局部)变量的值, 这个函数和它所处的环境, 称为闭包 
详细原理: 待补充
 函数柯里化: 把一个参数列表中的多个参数, 变成多个参数列表 

5.2.4 递归调用
- 一个函数/方法在函数/方法体内又调用了本身, 我们称之为递归调用;

5.2.5 控制抽象(函数参数的传递)
-  值调用: 把计算后的值传递过去 
-  名调用: 把代码传递过去 
注意: Java只有值调用, Scala既有值调用, 又有名调用;
[综合案例_ 实现While循环功能]
5.2.6 惰性加载
- 当函数返回值被声明为 lazy时, 函数的执行将被推迟, 直到我们首次对此取值, 该函数才会执行, 这种函数我们称之为惰性函数;
- 注意噢, 首次请求该函数的返回值时, 这个函数才会被执行!
注意: lazy不能修饰var类型的变量
 
                    
                     
                    
                 
                    
                

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号