• Scala函数式编程基础


    [toc]

    ## Scala函数式编程基础

    ### 1. 函数式编程内容

    > 1. `函数式编程基础`
    > 1. 函数定义/声明
    > 2. 函数运行机制
    > 3. 递归
    > - `难点 [最短路径,邮差问题,迷宫问题, 回溯]`
    > 4. 过程
    > 5. 惰性函数和异常
    > 2. `函数式编程高级`
    > 1. 值函数(函数字面量)
    > 2. 高阶函数
    > 3. 闭包
    > 4. 应用函数
    > 5. 柯里化函数,抽象控制...
    > 3. 在Scala中,函数式编程和面向对象编程融合在一起,学习函数式编程式需要OOP的知识,同样学习OOP需要函数式编程的基础。
    > 4. 学习顺序`: 函数式编程基础->面向对象编程->函数式编程高级;`

    ### 2. 函数式编程介绍

    > 在学习Scala中将`方法、函数、函数式编程和面向对象编程`明确一下:
    >
    > 1. 在scala中,方法和函数几乎可以等同(比如他们的定义、使用、运行机制都一样的),`只是函数的使用方式更加的灵活多样`。
    >
    > 2. 函数式编程是从编程方式(范式)的角度来谈的,可以这样理解:`函数式编程把函数当做一等公民`,充分利用函数、 支持的函数的多种使用方式。
    >
    > 1. 比如:在Scala当中,函数是一等公民,像变量一样,既可以作为函数的参数使用,也可以将函数赋值给一个变量. ,函数的创建不用依赖于类或者对象,而在Java当中,函数的创建则要依赖于类、抽象类或者接口。
    >
    > 3. 面向对象编程是以对象为基础的编程方式。
    >
    > 4. 在Scala中函数式编程和面向对象编程融合在一起了 。
    >
    > 5. 在学习Scala中将方法、函数、函数式编程和面向对象编程关系分析图:
    >
    > ![image-20210325145202072](assets/image-20210325145202072.png)
    >
    > 6. 小结:
    >
    > 1. "函数式编程"是一种"编程范式"(programming paradigm)。
    > 2. 它属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。
    > 3. 函数式编程中,将函数也当做数据类型,因此可以接受函数当作输入(参数)和输出(返回值)。
    > 4. 函数式编程中,最重要的就是函数。

    ### 3. 函数定义/声明

    为完成某一功能的程序指令(语句)的集合,称为函数。

    ~~~~scala
    def 函数名 ([参数名: 参数类型], ...)[[: 返回值类型] =] {
    语句...
    return 返回值
    }
    ~~~~

    > 1. 函数声明关键字为def (definition)
    > 2. [参数名: 参数类型], ...:表示函数的输入(就是参数列表), 可以没有。 如果有,多个参数使用逗号间隔
    > 3. 函数中的语句:表示为了实现某一功能代码块
    > 4. 函数可以有返回值,也可以没有
    > 1. 返回值形式1: `: 返回值类型 =`
    > 2. 返回值形式2: `=` 表示返回值类型不确定,使用类型推导完成
    > 3. 返回值形式3: 表示没有返回值,return 不生效
    > 5. `如果没有return ,默认以执行到最后一行的结果作为返回值。`
    > 6. 快速入门案例:

    ~~~~scala
    /**
    * @Date 2021/3/24 20:31
    * @Version 10.21
    * @Author DuanChaojie
    */
    object FunDemo01 {
    def main(args: Array[String]): Unit = {
    val n1 = 10
    val n2 = 20
    val oper = '-'
    val unit = getRes(n1, n2, oper)
    println(unit)
    }

    def getRes(n1: Int, n2: Int, oper: Char) = {
    if (oper == '+') {
    } else if (oper == '-') {
    println("res=" + (n1 - n2))
    }
    null
    }
    }
    ~~~~

    ### 4. 函数运行机制

    为了让大家更好的理解函数调用机制, 看一个案例,并画出示意图,这个很重要,比如`getSum` 计算两个数的和,并返回结果。

    ![image-20210325145908810](assets/image-20210325145908810.png)

    ### 5. ☆递归☆

    > 函数递归需要遵守的重要原则(总结):
    >
    > 1. 程序执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
    > 2. 函数的局部变量是独立的,不会相互影响
    > 3. 递归必须向退出递归的条件逼近,否则就是无限递归,死龟了:)
    > 4. 当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁。

    #### Scala递归课堂练习题

    ~~~~scala
    /**
    * @Date 2021/3/24 20:41
    * @Version 10.21
    * @Author DuanChaojie
    */
    object RecursiveExercise {
    def main(args: Array[String]): Unit = {
    val res1 = fib(6)
    println(res1) //8
    val res2 = fun(2)
    println(res2) //7
    val res3 = peach(1)
    println(res3) //1534
    }

    /**
    * 题1:斐波那契数
    * 请使用递归的方式,求出斐波那契数1,1,2,3,5,8,13...
    * 给你一个整数n,求出它的斐波那契数是多少?
    */
    def fib(n: Int): Int = {
    if (n == 1 || n == 2) {
    return 1
    }
    return fib(n - 1) + fib(n - 2)
    }

    /**
    * 题2:求函数值
    * 已知 f(1)=3; f(n) = 2*f(n-1)+1;
    * 请使用递归的思想编程,求出 f(n)的值?
    */
    def fun(n: Int): Int = {
    if (n == 1) {
    return 3
    }
    return 2 * fun(n - 1) + 1
    }

    /**
    * 题3:猴子吃桃子问题 有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!
    * 以后每天猴子都吃其中的一半,然后再多吃一个。
    * 当到第十天时,想再吃时(还没吃),发现只有1个桃子了。问题:最初共多少个桃子?
    */
    def peach(day: Int): Int = {
    if (day == 10) {
    return 1
    } else {
    return (peach(day + 1) + 1) * 2
    }
    }

    }
    ~~~~

    ### 6. Scala函数注意事项和细节讨论☆

    > 1. 函数的形参列表可以是多个, 如果函数没有形参,调用时 可以不带()
    >
    > 2. 形参列表和返回值列表的数据类型可以是值类型和引用类型。
    >
    > ~~~~scala
    > /**
    > * @Date 2021/3/24 20:57
    > * @Version 10.21
    > * @Author DuanChaojie
    > */
    > object FunDetails01 {
    > def main(args: Array[String]): Unit = {
    > val pig1 = new Pig
    > val pig2 = toPig(19,pig1)
    > println(pig2.name)
    >
    > //true
    > println(pig1.hashCode() == pig2.hashCode())
    > }
    >
    > def toPig(n: Int, pig: Pig): Pig = {
    > if (n >= 18) {
    > pig.name = "大胖猪"
    > }
    > return pig
    > }
    > }
    >
    > class Pig {
    > var name = "小胖猪"
    > }
    > ~~~~
    >
    > 3. Scala中的函数可以根据函数体最后一行代码自行推断函数返回值类型。那么在这种情况下,return关键字可以省略。
    >
    > ~~~scala
    > def getSum1(n1: Int, n2: Int): Int = {
    > n1 + n2
    > }
    > ~~~
    >
    > 4. 因为Scala可以自行推断,所以在省略return关键字的场合,返回值类型也可以省略
    >
    > ~~~~scala
    > def getSum1(n1: Int, n2: Int) = {
    > n1 + n2
    > }
    > ~~~~
    >
    > 5. 如果函数明确使用return关键字,那么函数返回就不能使用自行推断了,这时要明确写成 : 返回类型 = ,当然如果你什么都不写,即使有return 返回值为()
    >
    > ~~~~scala
    > /**
    > * @Date 2021/3/24 21:04
    > * @Version 10.21
    > * @Author DuanChaojie
    > */
    > object FunDetails02 {
    > def main(args: Array[String]): Unit = {
    > val sum1 = getSum1(1, 2)
    > println(sum1)
    > val res = getRes(3, 4)
    > //返回为 ()
    > println(res)
    > val sum2 = getSum2(5, 6)
    > //返回为 ()
    > println(sum2)
    > }
    >
    > // 如果写了return ,返回值类型就不能省略
    > def getSum1(n1: Int, n2: Int): Int = {
    > return n1 + n2
    > }
    >
    > /**
    > * 如果返回值这里什么什么都没有写,即表示该函数没有返回值
    > * 这时return无效
    > */
    > def getRes(n1: Int, n2: Int) {
    > return n1 + n2
    > }
    > }
    > ~~~~
    >
    > 6. 如果函数明确声明无返回值(声明Unit),那么函数体中即使使用return关键字也不会有返回值。
    >
    > ~~~~scala
    > /**
    > * 如果函数明确声明无返回值(声明Unit),那么函数体中即使使用return 关键字也不会有返回值
    > */
    > def getSum2(n1: Int, n2: Int): Unit = {
    > return n1 + n2
    > }
    > ~~~~
    >
    > 7. 如果明确函数无返回值或不确定返回值类型,那么返回值类型可以省略(或声明为Any)
    >
    > 8. Scala语法中任何的语法结构都可以嵌套其他语法结构(灵活),即:函数中可以再声明/定义函数,类中可以再声明类 ,方法中可以再声明/定义方法。
    >
    > ~~~~scala
    > /**
    > * @Date 2021/3/24 21:15
    > * @Version 10.21
    > * @Author DuanChaojie
    > */
    > object FunDetails03 {
    > def main(args: Array[String]): Unit = {
    >
    > // 底层private final
    > def main(): Unit = {
    > println("main ~")
    > }
    >
    > println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
    >
    > // 底层 private final sayHello$1()
    > def sayHello(): Unit = {
    > println("main say hello")
    >
    > // 底层 private final sayHello$2()
    > def sayHello(): Unit = {
    > println("say hello hello")
    > }
    > }
    >
    > }
    >
    > def sayHello(): Unit = {
    > println("say hello")
    > }
    > }
    > ~~~~
    >
    > 9. Scala函数的形参,在声明参数时,直接赋初始值(默认值),这时调用函数时,如果没有指定实参,则会使用默认值。如果指定了实参,则实参会覆盖默认值。
    >
    > ~~~scala
    > /**
    > * @Date 2021/3/24 22:08
    > * @Version 10.21
    > * @Author DuanChaojie
    > */
    > object FunDetails04 {
    > def main(args: Array[String]): Unit = {
    > println(sayOK())
    > println(sayOK("tom"))
    > }
    >
    > def sayOK(name: String = "jack"): String = {
    > return name + "\tok!"
    > }
    > }
    > ~~~
    >
    > 10. 如果函数存在多个参数,每一个参数都可以设定默认值,那么这个时候,传递的参数到底是覆盖默认值,还是赋值给没有默认值的参数,就不确定了(默认按照声明顺序[从左到右])。在这种情况下,可以采用带名参数。
    >
    > ~~~~scala
    > /**
    > * @Date 2021/3/24 22:10
    > * @Version 10.21
    > * @Author DuanChaojie
    > */
    > object FunDetails05 {
    > def main(args: Array[String]): Unit = {
    > mysqlCon()
    > println("--------------------------")
    > // 从左到右覆盖
    > mysqlCon("127.0.0.1", 777)
    > println("--------------------------")
    > // 如果我们希望指定覆盖某个默认值,则使用带名参数即可,比如修改用户名和密码
    > mysqlCon(user = "ddaimm", pwd = "1021")
    >
    >
    > println("**************************")
    >
    > // fun("v2") 报错
    > fun(p2 = "v2")
    > }
    >
    > def mysqlCon(add: String = "localhost", port: Int = 3306,
    > user: String = "root", pwd: String = "root"): Unit = {
    > println("add=" + add)
    > println("port=" + port)
    > println("user=" + user)
    > println("pwd=" + pwd)
    > }
    >
    > def fun(p1: String = "v1", p2: String) {
    > println(p1 + p2)
    > }
    > }
    > ~~~~
    >
    > 11. Scala 函数的形参默认是val的,因此不能在函数中进行修改。
    >
    > 12. Scala函数支持可变参数。
    >
    > ~~~~scala
    > /**
    > * args 是集合, 通过 for循环 可以访问到各个值。
    > * 案例演示: 编写一个函数sum ,可以求出 1到多个int的和
    > * 可变参数需要写在形参列表的最后。
    > */
    > object FunVarParameters {
    > def main(args: Array[String]): Unit = {
    > val res = sum(1, 2, 3, 4, 5, 5)
    > println(res)
    > }
    >
    > /**
    > *
    > * @param n
    > * @param args 可变参数需要放到最后
    > * @return
    > */
    > def sum(n: Int, args: Int*): Int = {
    > println("args.length = " + args.length)
    > var sum = n
    > for (item <- args) {
    > sum += item
    > }
    > sum
    > }
    > }
    > ~~~~
    >
    > 13. 函数练习题
    >
    > ~~~~scala
    > object FunDetails06 {
    > def main(args: Array[String]): Unit = {
    > println(f1)
    > println(f2)
    > }
    >
    > //输出 venassa
    > def f1 = "venassa" //等价于
    >
    > def f2() = {
    > "venassa"
    > }
    > }
    > ~~~~

    ### 7. 过程

    > 1. `将函数的返回类型为Unit的函数称之为过程(procedure)`,如果明确函数没有返回值,那么等号可以省略。
    > 2. 注意事项和细节说明:
    > 1. 注意区分: 如果函数声明时没有返回值类型,但是有 = 号,可以进行类型推断最后一行代码。这时这个函数实际是有返回值的,该函数并不是过程。
    > 2. 开发工具的自动代码补全功能,虽然会自动加上Unit,但是考虑到Scala语言的简单,灵活,最好不加。

    ### 8. 惰性函数

    > 1. `惰性计算`(尽可能延迟表达式求值)是许多函数式编程语言的特性。惰性集合在需要时提供其元素,无需预先计算它们,这带来了一些好处。首先,您可以将耗时的计算推迟到绝对需要的时候。其次,您可以创造无限个集合,只要它们继续收到请求,就会继续提供元素。函数的惰性使用让您能够得到更高效的代码。==Java 并没有为惰性提供原生支持,Scala提供了。==
    >
    > 2. 大数据推荐系统:
    >
    > ![image-20210325152730944](assets/image-20210325152730944.png)

    #### Java实现懒加载的代码

    ~~~scala
    /**
    * @Date 2021/3/25 15:30
    * @Version 10.21
    * @Author DuanChaojie
    * 比如常用的单例模式懒汉式实现时就使用了上面类似的思路实现
    */
    public class LazySingleton {
    private String property; //属性也可能是一个数据库连接,文件等资源

    public String getProperty() {
    if (property == null) {//如果没有初始化过,那么进行初始化
    property = initProperty();
    }
    return property;
    }

    private String initProperty() {
    return "property";
    }
    }
    ~~~

    #### 惰性函数

    ==当函数返回值被声明为lazy时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。==这种函数我们称之为惰性函数,在Java的某些框架代码中称之为懒加载(延迟加载)。

    ~~~~scala
    /**
    * @Date 2021/3/24 22:31
    * @Version 10.21
    * @Author DuanChaojie
    */
    object LazyDemo01 {
    def main(args: Array[String]): Unit = {
    lazy val res = sum(10, 20)
    println("-----------------")
    println("res=" + res) //在要使用res前,才执行
    }
    def sum(n1: Int, n2: Int): Int = {
    println("sum() 执行了..")
    return n1 + n2
    }
    }
    ~~~~

    > 注意事项和细节:
    >
    > 1. `lazy 不能修饰 var 类型的变量。`
    > 2. 不但是 在调用函数时,加了 lazy ,会导致函数的执行被推迟,我们在声明一个变量时,如果给声明了 lazy,那么变量值得分配也会推迟。 比如 `lazy val i = 10`

    ### 9. 异常

    > 1. Scala提供try和catch块来处理异常。try块用于包含可能出错的代码。catch块用于处理try块中发生的异常。可以根据需要在程序中有任意数量的try...catch块。
    >
    > 2. `语法处理上和Java类似,但是又不尽相同`
    >
    > 3. Java异常处理:
    >
    > ~~~~java
    > public class JavaExceptionDemo01 {
    > public static void main(String[] args) {
    > try {
    > // 可疑代码
    > int i = 0;
    > int b = 10;
    > int c = b / i; // 执行代码时,会抛出ArithmeticException异常
    > } catch (Exception e) {
    > e.printStackTrace();
    > } finally {
    > // 最终要执行的代码
    > System.out.println("java finally");
    > }
    >
    > }
    > }
    > ~~~~
    >
    > 4. Java异常处理的注意点:
    >
    > 1. java语言按照try—catch-catch...—finally的方式来处理异常
    > 2. 不管有没有异常捕获,都会执行finally, 因此通常可以在finally代码块中释放资源。
    > 3. 可以有多个catch,分别捕获对应的异常,这时需要把范围小的异常类写在前面,把范围大的异常类写在后面,否则编译错误。会提示 "`Exception 'java.lang.xxxxxx' has already been caught`"【案例演示】
    >
    > 5. Scala异常处理举例:
    >
    > ~~~~scala
    > /**
    > * @Date 2021/3/24 22:45
    > * @Version 10.21
    > * @Author DuanChaojie
    > */
    > object ScalaExceptionDemo01 {
    > def main(args: Array[String]): Unit = {
    > //说明
    > //1. 在scala中只有一个catch
    > //2. 在catch中有多个case, 每个case可以匹配一种异常 case ex: ArithmeticException
    > //3. => 关键符号,表示后面是对该异常的处理代码块
    > //4. finally 最终要执行的
    > try {
    > val r = 10 / 0
    > } catch {
    > case ex: ArithmeticException => {
    > println("捕获了除数为零的算数异常")
    > ex.printStackTrace()
    > }
    > case ex: Exception => println("捕获了异常")
    > } finally {
    > // 最终要执行的代码
    > println("scala finally...")
    > }
    > }
    > }
    > ~~~~

    #### Scala异常处理小结

    > 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注释来声明异常

    ~~~~scala
    object ThrowsComment {
    def main(args: Array[String]): Unit = {
    f11()
    }

    @throws(classOf[NumberFormatException]) //等同于Java NumberFormatException.class
    def f11() = {
    "abc".toInt
    }
    }
    ~~~~

    ### 10. 函数的课题练习

    ~~~~scala
    /**
    * @Date 2021/3/24 22:52
    * @Version 10.21
    * @Author DuanChaojie
    */
    object FunExercise {
    def main(args: Array[String]): Unit = {
    getTriangle(4)

    getMuTable(11)
    }

    /**
    * 函数可以没有返回值案例,编写一个函数,从终端输入一个整数打印出对应的金子塔。
    */
    def getTriangle(n: Int): Unit = {
    for (i <- 1 to n) {
    for (j <- 1 to n - i) {
    print(" ")
    }
    for (k <- 1 to 2 * i - 1) {
    print("*")
    }
    println()
    }
    }

    /**
    * 编写一个函数,从终端输入一个整数(1—9),打印出对应的乘法表
    */
    def getMuTable(n: Int): Unit = {
    for (i <- 1 to n) {
    for (j <- 1 to i) {
    print(s"$j * $i = ${i * j}\t")
    }
    println()
    }
    }
    }
    ~~~~

    ## ☆

  • 相关阅读:
    轮播图
    MySQL初认识
    css的动画
    jQuery 滚动监听总结
    DOM操作 练习
    Ajax总结
    日历 练习
    Jquery基础
    2018-07-14Java基础+基本数据类型+自动/强制数据类型转换+定义变量+运算符
    2018-07-13E-R图设计数据库+三大范式+修改用户密码+分配用户权限
  • 原文地址:https://www.cnblogs.com/huaobin/p/15764412.html
Copyright © 2020-2023  润新知