Scala编程学习之五-函数式编程基础

5.1函数式编程内容

5.1.1函数式编程内容

函数式编程基础
1)函数定义/声明
2)函数运行机制
3)递归 [推荐编程者递归来解决问题, 算法基础, 邮差问题,最短路径,背包问题, 迷宫,回溯 ]
4)过程
5)惰性函数和异常

函数式编程高级
1)值函数(函数字面量)
2)高阶函数
3)闭包
4)应用函数
5)柯里化函数,抽象控制…

5.1.2函数式编程
1)在scala中,函数式编程和面向对象编程融合在一起,学习函数式编程式需要oop的知识,同样学习oop需要函数式编程的基础。[矛盾]
2)关系如下图:
在这里插入图片描述

5.2函数式编程介绍
5.2.1几个概念的说明
在学习Scala中将方法、函数、函数式编程和面向对象编程明确一下:

1)在scala中,方法和函数几乎可以等同(比如他们的定义、使用、运行机制都一样的),只是函数的使用方式更加的灵活多样 [方法转函数]。

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


    //使用方法
    //先创建一个对象
    val dog = new Dog
    println(dog.sum(10,20))

    //方法转成函数
    val f1 = dog.sum _
    println("f1=" + f1)
    println("f1=" + f1(50,60))

    //函数, 求两个数的和
    val f2 = (n1:Int,n2:Int) => {
      n1 + n2 //函数体
    }

    println("f2=" + f2)
    println("f2=" + f2(5,6))


  }


}

class Dog {
  //方法
  def sum(n1:Int,n2:Int): Int = {
    n1 + n2
  }
}

2)函数式编程是从编程方式(范式)的角度来谈的,可以这样理解:函数式编程把函数当做一等公民,充分利用函数、 支持的函数的多种使用方式。
比如:
在Scala当中,函数是一等公民,像变量一样,既可以作为函数的参数使用,也可以将函数赋值给一个变量. ,函数的创建不用依赖于类或者对象,而在Java当中,函数的创建则要依赖于类、抽象类或者接口.
3)面向对象编程是以对象为基础的编程方式。
4)在scala中函数式编程和面向对象编程融合在一起了 。

5.3函数式编程介绍
“函数式编程"是一种"编程范式”(programming paradigm)。
它属于"结构化编程"的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。
函数式编程中,将函数也当做数据类型,因此可以接受函数当作输入(参数)和输出(返回值)。(增强了编程的粒度)
函数式编程中,最重要的就是函数。

5.4为什么需要函数
下面完成这样一个需求:
输入两个数,再输入一个运算符(+,-),得到结果.。

先使用传统的方式来解决,看看有什么问题没有?
1)代码冗余
2)不利于代码的维护

val n1 = 10
val n2 = 20
var oper = "-"
if (oper == "+") {
println("res=" + (n1 + n2))
} else if (oper == "-") {
println("res=" + (n1 - n2))
}

println("------做了其他的工作...")
val n3 = 10
val n4 = 20
oper = "-"
if (oper == "+") {
println("res=" + (n1 + n2))
} else if (oper == "-") {
println("res=" + (n1 - n2))
}

分析:
当一段功能代码出现多次时,编程时,就可以将这段功能代码抽取出来,做成函数,供调用。

5.5函数/方法的定义
5.5.1基本语法
def 函数名 ([参数名: 参数类型], …)[[: 返回值类型] =] {
语句… //完成某个功能
return 返回值
}
1)函数声明关键字为def (definition)
2)[参数名: 参数类型], …:表示函数的输入(就是参数列表), 可以没有。 如果有,多个参数使用逗号间隔
3)函数中的语句:表示为了实现某一功能代码块
4)函数可以有返回值,也可以没有
5)返回值形式1: // : 返回值类型 = 表示有返回值,并且指定了返回值的类型
6)返回值形式2: // = , 表示返回值类型,使用类型推导
7)返回值形式3: // 空的 ,表示没有返回值,即使有return 也不生效
8)如果没有return ,默认以执行到最后一行的结果作为返回值

5.5.2入门案例

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

    println(sum(1,2,'-'))
  }

  //说明
  //1. 如果我们返回的类型是多种的,则推荐使用类型推导
  def sum(n1: Int, n2: Int, oper: Char) = {

    //判断
    if (oper == '+') {
      n1 + n2
    } else if (oper == '-') {
      n1 - n2
    } else {
      null
    }
  }
}

代码分析示意图
在这里插入图片描述
5.7函数/方法-递归调用
5.7.1一个函数/方法在函数/方法体内又调用了本身,我们称为递归调用

递归调用快速入门
test(4)
test2(4)
在这里插入图片描述

5.7.3递归小练习

package com.smalltiger.ConditionalExpression

/**
  * Created by smalltiger on 2018/12/1.
  */
object Demo5 {
  def main(args: Array[String]): Unit = {
    var x1:Long = System.currentTimeMillis();
    println(f4(50))
    var x2:Long = System.currentTimeMillis();
    println(x2-x1)
    println("=========================")
    //println(f4(8))
    //println(f3(9))
  }

  //斐波那契数
  def f1(n: Int): Int = {
    if (n >= 3) {
      f1(n - 1) + f1(n - 2)
    } else {
      1
    }
  }

  //斐波那契数优化方式一
  def f4(n: Int): Int = {
    if(n<3){
      1
    }else{
      var a:Int = 1
      var b:Int = 1
      var c:Int = 0

      for(i <- 3 to n)
      {
          c = a + b
          a = b
          b = c
      }
      c
    }
  }

  /*
  题2:求函数值 [演示]
已知 f(1)=3; f(n) = 2*f(n-1)+1;
请使用递归的思想编程,求出 f(n)的值
   */
  def f2(n: Int): Int = {
    if (n >= 2) {
      2 * f2(n - 1) + 1
    } else {
      3
    }
  }

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

5.8函数/方法注意事项和细节讨论

1)函数的形参列表可以是多个, 如果函数没有形参,调用时 可以不带()

2)形参列表和返回值列表的数据类型可以是值类型和引用类型。

3)Scala中的函数可以根据函数体最后一行代码自行推断函数返回值类型。那么在这种情况下,return关键字可以省略

4)因为Scala可以自行推断,所以在省略return关键字的场合,返回值类型也可以省略

5)如果函数明确使用return关键字,那么函数返回就不能使用自行推断了,这时要明确写成 : 返回类型 = ,当然如果你什么都不写,即使有return 返回值为()

6)如果函数明确声明无返回值(声明Unit),那么函数体中即使使用return关键字也不会有返回值

7)如果明确函数无返回值或不确定返回值类型,那么返回值类型可以省略(或声明为Any)

8)Scala语法中任何的语法结构都可以嵌套其他语法结构(灵活),即:函数/方法中可以再声明/定义函数/方法,类中可以再声明类

9)Scala函数的形参,在声明参数时,直接赋初始值(默认值),这时调用函数时,如果没有指定实参,则会使用默认值。如果指定了实参,则实参会覆盖默认值

10)如果存在多个参数,每一个参数都可以设定默认值,那么这个时候,传递的参数到底是覆盖默认值,还是赋值给没有默认值的参数,就不确定了(默认按照声明顺序[从左到右])。在这种情况下,可以采用带名参数

11)scala 函数的形参默认是val的,因此不能在函数中进行修改.

12)递归函数未执行之前是无法推断出来结果类型,在使用时必须有明确的返回值类型

13)Scala函数支持可变参数

14)代码演示

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

    //1. 如果函数没有形参,调用时 直接写名字
    sayHello
    //2. 形参列表和返回值列表的数据类型可以是值类型和引用类型, 这点和java一样
    //3. Scala中的函数可以根据函数体最后一行代码自行推断函数返回值类型。那么在这种情况下,return关键字可以省略
    //4.

    println(sum4(10, 30)) //()而不是 40

    val res = f10(10)


    //8.Scala语法中任何的语法结构都可以嵌套其他语法结构(灵活),即:函数/方法中可以再声明/定义函数/方法,类中可以再声明类

    def sayHello2(): Unit = {
      println("sayHello")
    }

    val f11 = (s: String) => {
      println("s=" + s)
      val f12 = (s: String) => {
        println("f12 s=" + s)
      }
      f12("yes")
    }

    class A {}

    //9.测试
    sayOK()
    sayOK("深圳")

    //10测试
    //mysqlCon("192.168.11.11")//错误,因为没有给port值
    //使用命名参数来指定某个形参的值,这样就更灵活
    mysqlCon("192,168.11.11", port = 8888)

    //13测试
    println(sum10(1,2,3)) //6
    println(sum10(1,2,3,4,5)) // 15


  }

  //9.Scala函数的形参,在声明参数时,直接赋初始值(默认值),这时调用函数时,如果没有指定实参,则会使用默认值。如果指定了实参,则实参会覆盖默认值
  def sayOK(s: String = "北京"): Unit = {
    println("sayOk =" + s)
  }

  //10.如果存在多个参数,每一个参数都可以设定默认值,那么这个时候,传递的参数到底是覆盖默认值,还是赋值给没有默认值的参数,就不确定了(默认按照声明顺序[从左到右])。在这种情况下,可以采用带名参数

  def mysqlCon(add: String = "localhost", port: Int,
               user: String = "root", pwd: String = "root"): Unit = {
    println("add=" + add)
    println("port=" + port)
    println("user=" + user)
    println("pwd=" + pwd)
  }

  //12.递归函数未执行之前是无法推断出来结果类型,在使用时必须有明确的返回值类型
  //   底层是编译原理来决定.
  def f8(n: Int):Int = { //? 错误,递归不能使用类型推断,必须指定返回的数据类型
    if (n <= 0)
      1
    else
      n * f8(n - 1)
  }

  //13.在scala中,支持可变参数
  //   注意可变参数只能放在最后
  //案例演示: 编写一个函数sum ,可以求出  1到多个int的和
  def sum10(n:Int,args:Int*): Int = {
    var res = n
    for (i<-args) {
      res += i
    }
    res
  }


  def sum(n1: Int, n2: Int): Int = {
    //    return n1 + n2
    n1 + n2
  }

  //4. 因为Scala可以自行推断,所以在省略return关键字的场合,返回值类型也可以省略
  def sum2(n1: Int, n2: Int) = {
    //    return n1 + n2
    n1 + n2
  }

  //5如果函数明确使用return关键字,那么函数返回就不能使用自行推断了,这时要明确写成 : 返回类型 =  ,当然如果你什么都不写,即使有return 返回值为()

  //  def sum3(n1:Int,n2:Int) = {
  //    return n1 + n2
  //  }
  //如果你什么都不写,即使有return 返回值为()
  def sum4(n1: Int, n2: Int) {
    //    return n1 + n2
    return n1 + n2
  }

  //6.如果函数明确声明无返回值(声明Unit),那么函数体中即使使用return关键字也不会有返回值
  def sum5(n1: Int, n2: Int): Unit = {
    //    return n1 + n2
    return n1 + n2
  }

  //7.如果明确函数无返回值或不确定返回值类型,那么返回值类型可以省略(或声明为Any)
  def f10(n: Int): Any = {
    if (n == 1) {
      10
    } else {
      "yes"
    }
  }


  def sayHello(): Unit = {
    println("sayHello")
  }
}

5.9过程
5.9.1基本介绍

将函数的返回类型为Unit的函数称之为过程(procedure),如果明确函数没有返回值,那么等号可以省略
5.9.2案例演示
在这里插入图片描述
5.9.3注意事项和细节说明

1)注意区分: 如果函数声明时没有返回值类型,但是有 = 号,可以进行类型推断最后一行代码。这时这个函数实际是有返回值的,该函数并不是过程。(这点在讲解函数细节的时候讲过的.)
2)开发工具的自动代码补全功能,虽然会自动加上Unit,但是考虑到Scala语言的简单,灵活,最好不加.

5.10惰性函数
5.10.1看一个应用场景
惰性计算(尽可能延迟表达式求值)是许多函数式编程语言的特性。惰性集合在需要时提供其元素,无需预先计算它们,这带来了一些好处。首先,您可以将耗时的计算推迟到绝对需要的时候。其次,您可以创造无限个集合,只要它们继续收到请求,就会继续提供元素。函数的惰性使用让您能够得到更高效的代码。Java 并没有为惰性提供原生支持,Scala提供了。
5.10.2应用的案例
在这里插入图片描述
5.10.3代码演示

object LazyDemo {
  def main(args: Array[String]): Unit = {
    lazy val res = sum(1,2)
    println("-----------------------")
    println("res=" + res) //当需要使用到res时,就会真正的开始计算
  }

  //将来这个计算方法是非常耗费运力
  def sum(n1:Int,n2:Int): Int = {
    println("sum 被调用..")
    n1 + n2
  }
}

5.10.4惰性函数介绍
当函数返回值被声明为lazy时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数,在Java的某些框架代码中称之为懒加载(延迟加载)。
5.10.5注意事项和细节
1)lazy 不能修饰 var 类型的变量
2)不但是 在调用函数时,加了 lazy ,会导致函数的执行被推迟,我们在声明一个变量时,如果给声明了 lazy ,那么变量值得分配也会推迟。 比如 lazy val i = 10

5.11异常
5.11.1介绍
Scala提供try和catch块来处理异常。try块用于包含可能出错的代码。catch块用于处理try块中发生的异常。可以根据需要在程序中有任意数量的try…catch块。
语法处理上和Java类似,但是又不尽相同

Java异常处理
public class JavaException {
public static void main(String[] args) {

    /*
    可以有多个catch,分别捕获对应的异常,这时需要把范围小的异常类写在前面,把范围大的异常类写在后面,否则编译错误。会提示 "Exception 'java.lang.xxxxxx' has already been caught"
     */
    try {
        // 可疑代码
        int i = 0;
        int b = 10;
        int c = b / i; // 执行代码时,会抛出ArithmeticException异常
    } catch (ArithmeticException e) {
        e.printStackTrace();
    } catch (Exception e) {
        //e.printStackTrace();
        System.out.println(e.getMessage());
    } finally {
        // 最终要执行的代码
        System.out.println("java finally");
    }
    System.out.println("程序继续执行~~");


}

}
5.11.3Java异常处理的注意点.

1)java语言按照try—catch-catch…—finally的方式来处理异常
2)不管有没有异常捕获,都会执行finally, 因此通常可以在finally代码块中释放资源
3)可以有多个catch,分别捕获对应的异常,这时需要把范围小的异常类写在前面,把范围大
4)常类写在后面,否则编译错误。会提示 “Exception ‘java.lang.xxxxxx’ has already been caught”

5.11.4Scala异常处理举例

object ScalaException {
  def main(args: Array[String]): Unit = {
    //scala中去掉所谓的checked(编译) 异常
    //设计者认为,如果程序员编程时,认为某段代码可疑,就直接try并处理

    //说明
    //1. 如果代码可疑,使用try进行处理
    //2. 在catch中,可以有多个case ,对可能的异常进行匹配
    //3. case ex: Exception => println("异常信息=" + ex.getMessage)
    //   (1) case 是一个关键字
    //   (2) ex: Exception  异常的种类
    //   (3) => 表明后的代码是对异常进行处理,如果处理的代码有多条语句可以{}扩起
    //4. 在scala中把范围小的异常放在后面,语法不会报错,但是不推荐
    //5. 如果捕获异常,代码即使出现异常,程序也不会崩溃。
    try {
      var res = 10 / 0
    } catch {

      case ex: ArithmeticException => {
        println("算术异常=" + ex.getMessage)
        println("111")
        println("222")
      }
      case ex: Exception => println("异常信息=" + ex.getMessage)

    } finally {
      println("finaly的代码...")
    }

    println("程序继续....")


  }
}

5.11.5Scala异常处理小结

1)我们将可疑代码封装在try块中。 在try块之后使用了一个catch处理程序来捕获异常。如果发生任何异常,catch处理程序将处理它,程序将不会异常终止。
2)Scala的异常的工作机制和Java一样,但是Scala没有“checked(编译期)”异常,即Scala没有编译异常这个概念,异常都是在运行的时候捕获处理。

3)用throw关键字,抛出一个异常对象。所有异常都是Throwable的子类型。throw表达式是有类型的,就是Nothing,因为Nothing是所有类型的子类型,所以throw表达式可以用在需要类型的地方

def main(args: Array[String]): Unit = {
      val res = test()
      println(res.toString)
  }
  def test(): Nothing = {
   throw new Exception("不对")
  }

4)在Scala里,借用了模式匹配的思想来做异常的匹配,因此,在catch的代码里,是一系列case子句来匹配异常。【前面案例可以看出这个特点, 模式匹配我们后面详解】,当匹配上后 => 有多条语句可以换行写,类似 java 的 switch case x: 代码块…
5)异常捕捉的机制与其他语言中一样,如果有异常发生,catch子句是按次序捕捉的。因此,在catch子句中,越具体的异常越要靠前,越普遍的异常越靠后,如果把越普遍的异常写在前,把具体的异常写在后,在scala中也不会报错(不报错,但是不推荐),但这样是非常不好的编程风格。
6)finally子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作,这点和Java一样。
7)Scala提供了throws关键字来声明异常。可以使用方法定义声明异常。 它向调用者函数提供了此方法可能引发此异常的信息。 它有助于调用函数处理并将该代码包含在try-catch块中,以避免程序异常终止。在scala中,可以使用throws注释来声明异常

def main(args: Array[String]): Unit = {
    f11()
  }
  @throws(classOf[NumberFormatException])//等同于NumberFormatException.class
  def f11()  = {
    "abc".toInt
  }

猜你喜欢

转载自blog.csdn.net/smsmtiger/article/details/84719704
今日推荐