5.1 函数式编程内容及介绍顺序说明
5.1.1 函数式编程内容
-函数式编程基础
函数定义/声明
函数运行机制
递归(难点,[最短路径,邮差问题,迷宫问题,回溯])
过程
惰性函数和异常
-函数式编程高级
值函数(函数字面量)
高阶函数
闭包
应用函数
柯里化函数,抽象控制...
5.1.2 函数式编程介绍顺序说明
1) 在Scala中,函数式编程和面向对象编程融合在一起,学习函数式编程需要oop的知识,同样学习oop需要函数式编程的基础
2) 介绍顺序:函数式编程基础->面向对象编程->函数式编程
5.2函数式编程介绍
5.2.1 几个相关概念的说明
在学习Scala中将方法、函数、函数式编程和面向对象编程明确一下:
1) 在Scala中,方法和函数几乎可以等同(比如它们的定义、使用、运行机制都是一样的),只是函数的使用方式更加的灵活多样
2) 函数式编程是从编程方式(范式)的角度来谈的,可以这样理解:函数式编程把函数当作一等公民,充分利用函数、只是函数的多种使用方式,比如:在Scala中,函数是一等公民,像变量一样,既可以作为函数的参数使用,也可以将函数赋值给一个变量,函数的创建不用依赖于类或者对象,而在Java当中,函数的创建则要依赖于类、抽象类或者接口
3) 面向对象编程是以对象为基础的编程方式
4) 在Scala中函数式编程和面向对象编程融合在一起了
5.2.2 Scala中方法、函数、函数式编程和面向对象编程关系分析图
5.2.3 函数式编程小结
1) “函数式编程”是一种“编程范式”(programming paradigm)
2) 它属于“结构化编程”的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用
3) 函数式编程中,将函数也当做数据类型,因为也可以接受函数当作输入(参数)和输出(返回值)
4) 函数式编程中,最重要的就是函数
5.4 函数的定义
5.4.1 基本语法
def 函数名([参数名:参数类型],...)[[:返回值类型]=] {
语句...
return 返回值
}
1) 函数声明关键字为def(definition)
2) [参数名:参数类型],...: 表示函数的输入(就是参数列表),可以没有。如果有,多个参数使用逗号间隔
3) 函数中的语句:表示为了实现某一功能代码块
4) 函数可以有返回值,也可以没有
返回值形式1: : 返回值类型 =
返回值形式2: = 表示返回值类型不确定,需要类型推导完成
返回值形式3: 表示没有返回值,return不生效
5) 如果没有return,默认以执行到最后一行的结果作为返回值
5.4.2 快速入门案例
object boke_demo01 { def main(args: Array[String]): Unit = { val n1 = 10 val n2 = 20 println("res=" + getRes(1, 2, ')')) } //定义函数/方法 def getRes(n1: Int, n2: Int, oper: Char) = { if (oper == '+') { n1 + n2 //返回 } else if (oper == '-') { n1 - n2 } else { //返回null null } } }
5.5 函数-调用机制
5.5.1 函数-调用过程
为了更好的理解函数调用机制,看一个案例,比如getSum计算两个数的和,并返回结果
5.5.2 函数递归调用的重要规则和小结
1) 程序执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
2) 函数的局部变量是独立的,不会相互影响
3) 递归必须向退出递归的条件逼近,否则就无限递归了
4) 当一个函数执行完毕,或者遇到return,就返回,遵守谁调用,就将结果返回给谁
5.6 函数注意事项和细节讨论
1) 函数的形参列表可以是多个,如果函数没有形参,调用时可以不带()
2) 形参列表和返回值列表的数据类型可以是值类型和引用类型
object boke_demo01 { def main(args: Array[String]): Unit = { //形参列表和返回值列表的数据类型可以是值类型和引用类型 val tiger = new Tiger val tiger2 = test01(10, tiger) println(tiger2.name) // jack println(tiger.name) // jack println(tiger.hashCode() + " " + tiger2.hashCode()) } def test01(n1: Int, tiger: Tiger): Tiger = { println("n1=" + n1) tiger.name = "jack" tiger } } class Tiger { //一个名字属性 var name = "" }
3) Scala中的函数可以根据函数体最后一行代码自行推断函数返回值类型,那么在这种情况下return关键字可以省略
def getSum(n1: Int, n2: Int): Int = { n1 + n2 }
4) 因为Scala可以自行推断,所以在省略关键字return关键字的场合,返回值类型也可以省略
def getSum(n1: Int, n2: Int) = { n1 + n2 }
5) 如果函数明确使用return关键字,那么函数返回就不能使用自行推断了,这时要明确写成 : 返回值类型 = ,当然,如果什么都不写,即使有return返回值也为()
object boke_demo01 { def main(args: Array[String]): Unit = { println(getSum2(10, 30)) // () println(getSum3(9, 9)) //() } //如果写了return ,返回值类型就不能省略 def getSum(n1: Int, n2: Int): Int = { return n1 + n2 } //如果返回值这里什么什么都没有写,即表示该函数没有返回值 //这时return无效 def getSum2(n1: Int, n2: Int) { return n1 + n2 } }
6) 如果函数明确声明无返回值(声明Unit),那么函数体中即使使用return关键字也不会有返回值
//如果函数明确声明无返回值(声明Unit),那么函数体中即使使用return关键字也不会有返回值 def getSum3(n1: Int, n2: Int): Unit = { return n1 + n2 }
7) 如果明确函数无返回值或不确定返回值类型,那么返回值类型可以省略(或声明为Any)
8) Scala语法中任何的语法结构都可以嵌套其它语法结构(灵活),即:函数中可以再声明/定义函数,类中可以再声明类,方法中可以再声明/定义方法
object boke_demo01 { def main(args: Array[String]): Unit = { def f1(): Unit = { //ok private final println("f1") } println("ok~~") def sayOk(): Unit = { // private final sayOk$1 () println("main sayOk") def sayOk(): Unit = { // private final sayOk$2 () println("sayok sayok") } } } def sayOk(): Unit = { //成员 println("main sayOk") } }
9) Scala函数的形参,在声明参数时,直接赋初始值(默认值),这时调用函数时,如果没有指定实参,则会使用默认值。如果指定了实参,则实参会覆盖默认值
object boke_demo01 { def main(args: Array[String]): Unit = { println(sayOk("mary")) } //name形参的默认值jack def sayOk(name: String = "jack"): String = { return name + " ok! " } }
10) 如果函数存在多个参数,每一个参数都可以设定默认值,那么这个时候,传递的参数到底是覆盖默认值,还是赋值给没有默认值的参数,就不确定了(默认按照声明顺序[从左到右])。在这种情况下,可以采用带名参数
object boke_demo01 { def main(args: Array[String]): Unit = { // mysqlCon() // mysqlCon("127.0.0.1", 7777) //从左到右覆盖 //如果我们希望指定覆盖某个默认值,则使用带名参数即可,比如修改用户名和密码 mysqlCon(user = "tom", pwd = "123") //f6("v2") // (错误) f6(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 f6(p1: String = "v1", p2: String) { println(p1 + p2); } }
11) 递归函数未执行之前是无法推断出来结果类型,在使用时必须有明确的返回值类型
def f8(n: Int) = { //?错误,递归不能使用类型推断,必须指定返回的数据类型 if (n < 0) 1 else n * f8(n - 1) }
12) Scala函数支持可变参数
-基本语法
//支持0到多个参数
def sum(args : Int*) : Int = {
}
//支持1到多个参数
def sum(n1 : Int, args : Int*) : Int = {
}
-使用注意事项
1) args是集合,通过for循环可以访问到各个值
2) 可变参数需要写在形参列表的最后
5.8 过程
5.8.1 基本概念
将函数的返回值类型为Unit的函数称之为过程(procedure),如果明确函数没有返回值,那么等号可以省略
//f10没有返回值,可以使用Unit来说明 //这时,这个函数我们也叫过程(procedure) def f10(name: String): Unit = { println(name + "hello") }
5.8.2 注意事项和细节说明
1) 注意区分:如果函数声明时没有返回值类型,但是有等号(=),可以进行类型推断最后一行代码。这时这个函数实际是有返回值的,该函数并不是过程
2) 开发工具的自动代码补全功能,虽然会自动加上Unit,但是考虑到Scala语言的简单,灵活,最好不加
5.9 惰性函数
5.9.1 看一个应用场景
惰性计算(尽可能延迟表达式求值)是许多函数式编程语言的特性。惰性集合在需要时提供其元素。无需预先计算它们,这带来了一些好处。首先,可以将耗时的计算推迟到绝对需要的时候。其次,可以创造无限个集合,只要它们收到请求,就会继续提供元素。函数的惰性使用能够得到更高效的代码。Java并没有为惰性提供原生支持,而Scala提供了
5.9.2 画图说明[大数据推荐系统]
5.9.3 惰性函数介绍
当函数返回值被声明为lazy时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数
5.9.4 案例演示
object boke_demo01 { def main(args: Array[String]): Unit = { lazy val res = sum(10, 20) println("-----------------") println("res=" + res) //在要使用res 前,才执行 } //sum函数,返回和 def sum(n1: Int, n2: Int): Int = { println("sum() 执行了..") //输出一句话 return n1 + n2 } }
5.9.6 注意事项和细节
1) lazy不能修饰var类型的变量
2) 不但是在调用函数时,加了lazy,会导致函数的执行被推迟,我们在声明一个变量时,如果给声明了lazy,那么变量值的分配也会推迟
5.10 异常
5.10.1 基本介绍
1) Scala提供了try和catch块来处理异常,try块用语包含可能出错的代码,catch块用于处理try块中发生的异常。可以根据需要在程序中可以有任意数量的try...catch块
2) 语法上和Java类似,但是又不尽相同
5.10.2 Java异常处理回顾
public class JavaExceptionDemo01 { public static void main(String[] args) { try { // 可疑代码 int i = 0; int b = 10; int c = b / i; // 执行代码时,会抛出ArithmeticException异常 } catch (ArithmeticException ex) { ex.printStackTrace(); } catch (Exception e) { //java中不可以把返回大的异常写在前,否则报错!! e.printStackTrace(); } finally { // 最终要执行的代码 System.out.println("java finally"); } System.out.println("ok~~~继续执行..."); } }
5.10.3 Java异常处理的注意点
1) Java语言按照try-catch-catch...-finally的方式来处理异常
2) 不管有没有异常捕获,都会执行finally
3) 可以有多个catch,分别捕获对应的异常,这时需要把范围小的异常类写在前面,把范围大的异常类写在后面,否则编译错误。会提示 "Exception 'java.lang.xxxxxx' has already been caught"
5.10.4 Scala异常处理案例演示
object boke_demo01 { def main(args: Array[String]): Unit = { try { val r = 10 / 0 } catch { //说明 //1. 在scala中只有一个catch //2. 在catch中有多个case, 每个case可以匹配一种异常 case ex: ArithmeticException //3. => 关键符号,表示后面是对该异常的处理代码块 //4. finally 最终要执行的 case ex: ArithmeticException => { println("捕获了除数为零的算数异常") } case ex: Exception => println("捕获了异常") } finally { // 最终要执行的代码 println("scala finally...") } println("ok,继续执行~~~~~") } }
5.10.5 Scala异常处理小结
1) 我们将可疑代码封装在try块中,在try块之后使用了一个catch处理程序来捕获异常。如果发生任何异常,catch处理程序将处理它,程序将不会异常终止
2) Scala的异常的工作机制和Java一样,但是Scala没有“checked(编译期)”异常,即Scala没有编译异常这个概念,异常都是在运行的时候捕获处理
3) 用throw关键字,抛出一个异常对象。所有异常都是Throwable的子类行,throw表达式是有类型的,就是Nothing,因为Nothing是所有类型的子类型,所以throw表达式可以用在需要类型的地方
def test(): Nothing = { throw new ArithmeticException("算数异常") }
4) 在Scala中,借用了模式匹配的思想来做异常的匹配,因此,在catch的代码里,是一系列的case子句来匹配异常,当匹配上后 => 有多条语句可以换行写,类似Java的 switch case x: 代码块...
5) 异常捕捉的机制与其它语言中一样,如果有异常发生,catch子句是按次序捕捉的。因此,在catch子句中,越具体的异常越要靠前,越普遍的异常越靠后。如果把越普遍的异常写在前面,越具体的异常写在后面,在Scala中也不报错,但这样是非常不好的编程风格
6) finally子句用于执行不管是正常处理还是有异常发生时都需要执行的步骤,一般用于对象的清理工作,这点和Java一样
7) Scala提供了throws关键字来声明异常,可以使用方法定义声明异常,它向调用者函数提供了此方法可能引发此异常的信息。它有助于调用函数处理并将该代码包含在try-catch块中,以避免程序异常终止。在Scala中可以使用throws注释来声明异常
object boke_demo01 { def main(args: Array[String]): Unit = { f11() } @throws(classOf[NumberFormatException]) //等同于 NumberFormatException.class def f11() = { "abc".toInt } }