二, Scala 函数式编程详解

五, 函数式编程

  1. 面向对象编程(OOP)
  • 面向对象思想: 使用对象来映射现实中的事物, 使用对象的关系来描述事事物之间的联系
  • 面向对象编程: 把要解决的问题按照一定的规则划分为多个独立的对象, 这些对象拥有各自的属性和方法, 通过调用对象的方法来解决问题;

[举个栗子]

用户通过JDBC连接和读取数据库

  • 对象: 用户;
  • 属性: 用户名, 密码;
  • 行为(方法): 登录, 连接JDBC, 读取数据库;

对象的本质: 对数据和行为的一个封装

  1. 函数式编程(FP)
  • 面向过程编程: 分析解决问题所需要的步骤, 然后用函数把这些步骤一一实现;

[举个栗子]

用户通过JDBC连接和读取数据库

  • 请求->用户名,密码->连接JDBC->读取数据库

函数的本质: 函数可以当做一个值进行传递

5.1 函数基础

  • Scala 既是一个完全面向对象编程语言, 万物皆对象, 不像Java那样是半吊子OO(基本数据类型经过包装才能作为对象);
  • Scala 又是一个完全函数式的编程语言, 万物皆为函数;

5.1.1 函数基本语法

请添加图片描述

5.1.2 函数和方法的区别

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

请添加图片描述

5.1.3 函数的参数

  1. 可变参数; def function(args : String*)
  2. 参数默认值; def function(args : String = “default args”)
  3. 带名参数; 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) //实参

从上面的栗子可以得出两点:

  1. 匿名函数的函数名写法: var 匿名函数名称 : (参数列表) => 返回值类型 , 如: var f1: (name: String, age: Int) => Unit, 有这么个匿名函数, 返回值为空, 参数列表包括String类型的name和Int类型的age , 我们让它的函数名为f,
  2. 把匿名函数作为另一个函数的参数: def f2(匿名或普通函数的函数名 : (参数类型 => 返回值类型)) , 如, def f2(f1: (name: String, age: Int) => Unit, addr: String)

5.2.1.2 匿名函数简化规则

匿名函数至简原则:

  1. 参数类型可以省略, 会根据形参进行自动推导;
  2. 类型省略后, 只有一个参数的, 可以省略小括号; 没有参数和参数超过1的. 不能省略圆括号;
  3. 匿名函数只有一行, 则大括号也可省略;
  4. 参数只出现一次, 则参数省略且后面的参数可用_代替;

在举栗子之前, 先明确一个知识点:

如何定义一个变量, 这个变量的类型是一个参数类型为(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 控制抽象(函数参数的传递)

  1. 值调用: 把计算后的值传递过去

  2. 名调用: 把代码传递过去

注意: Java只有值调用, Scala既有值调用, 又有名调用;

[综合案例_ 实现While循环功能]

5.2.6 惰性加载

  • 函数返回值被声明为 lazy时, 函数的执行将被推迟, 直到我们首次对此取值, 该函数才会执行, 这种函数我们称之为惰性函数;
  • 注意噢, 首次请求该函数的返回值时, 这个函数才会被执行!

注意: lazy不能修饰var类型的变量

posted @ 2022-05-26 20:30  青松城  阅读(169)  评论(0)    收藏  举报