一、函数基础
函数式是基于数学理论的函数概念,类似于 y = f(x)
1. 函数定义
1.1 语法
/* 函数结构:def funcName(param1 : type1, param2 : type2,...): type = { statement }*/
def sum(x : Int, y : Int): Int = {return x + y
}
1.2 案例
object TestFunctionDefine {def main(args: Array[String]): Unit = {// 函数 1:无参,无返回值def test1(): Unit = {println("1:无参,无返回值")}test1()// 函数 2:无参,有返回值def test2(): String = {return "2:无参,有返回值"}println(test2())// 函数 3:有参,无返回值def test3(s: String): Unit = {println("3:有参,无返回值" + s)}test3("Hi")// 函数 4:有参,有返回值def test4(s: String): String = {return s + "4:有参,有返回值"}println(test4("hello "))// 函数 5:多参,无返回值def test5(name: String, age: Int): Unit = {println(s"${name}今年${age}岁")}test5("阿豪", 18)// 函数 6:多参,有返回值def test6(a: Int, b: Int): Int = {return a + b}println(test6(10, 20))}
}
2. 函数VS方法
-
概念:
- 函数:为完成某一功能的程序语句的集合 (区别于函数式编程的函数)
- 方法:类中的函数
object TestFunctionAndMethod {def main(args: Array[String]): Unit = {// 定义函数def sayHi(name : String): Unit = {println("hi," + name)}// 函数调用sayHi("张三")// 方法调用TestFunctionAndMethod.sayHi("李四")// 获取方法返回值val result = TestFunctionAndMethod.sayHello("王五")println(result)}// 定义方法def sayHi(name : String): Unit = {println("Hi," + name)}// 定义有返回值的方法def sayHello(name : String): String = {return "Hello," + name} }
-
语法:
- Scala 语言可以在任何的语法结构中声明任何的语法
- 函数没有重载和重写的概念;方法可以进行重载和重写
- Scala 中函数可以嵌套定义
object TestFunctionAndMethod {// 方法可以进行重载和重写,程序可以执行def main(): Unit = {}def main(args: Array[String]): Unit = {// Scala 语言可以在任何的语法结构中声明任何的语法import java.util.Datenew Date()// 函数没有重载和重写的概念,程序会报错def test(): Unit = {println("无参,无返回值")}test()def test(name:String): Unit = {println()}// Scala 中函数可以嵌套定义def test2(): Unit = {def test3(name:String): Unit = {println("函数可以嵌套定义")}}} }
3. 函数参数
object TestFunctionParam {def main(args: Array[String]): Unit = {// 1.可变参数,且可变参数一般放在参数列表的最后def f1(s: String*): Unit = { // 可变参数底层是 ArrayBuffer[String]println(s)}f1() // WrappedArray()f1("tom") // WrappedArray(tom)f1("tom", "jerry") // WrappedArray(tom, jerry)def f2(s1: String, s2: String*): Unit = {println("s1:" + s1 + ", s2:" + s2)}f2("jack")f2("jack", "rose", "james")// 2.参数默认值,有默认值的参数放置在参数列表的后面def f3(name: String, school: String = "HighSchool"): Unit = {println("my name is " + name + ", my school is " + school)}f3("james")f3("james", "GoodUniversity")// 3.带名参数def f4(name: String = "abc", age: Int): Unit = {println(s"${name}今年已经${age}岁了")}f4("rock", 25)f4(age = 30, name = "curry")f4(age = 18)}
}
4. 函数至简原则
重点
1.4.1 要点
- return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
- 如果函数体只有一行代码,可以省略花括号
- 返回值类型如果能够推断出来,那么可以省略(: 和返回值类型一起省略)
- 如果有 return,则不能省略返回值类型,必须指定
- 如果函数明确声明 Unit,那么即使函数体中使用 return 关键字也不起作用
- Scala 如果期望是无返回值类型,可以省略等号
- 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
- 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
- 如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
1.4.2 案例
object TestFunctionSimplify {def main(args: Array[String]): Unit = {// 函数标准语法def f0(name: String): String = {return name}println(f0("jack"))// 简化:// 1. return 可以省略,Scala 会使用函数体的最后一行代码作为返回值def f1(name: String): String = {name}println(f1("jack"))// 2. 如果函数体只有一行代码,可以省略花括号def f2(name: String): String = nameprintln(f2("jack"))// 3. 返回值类型如果能够推断出来,那么可以省略(: 和返回值类型一起省略)def f3(name: String) = nameprintln(f3("jack"))// 4. 如果有 return,则不能省略返回值类型,必须指定/* error:def f4(name: String) = {return name}*/// 5. 如果函数明确声明 Unit,那么即使函数体中使用 return 关键字也不起作用def f5(name: String): Unit = { // 函数返回值为 ()return name // 失效}println(f5("jack")) // ()// 6. Scala 如果期望是无返回值类型,可以省略等号def f6(name: String) { println(name)}println(f6("jack"))// 7. 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加def f7() { println("jack")}f7()f7// 8. 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略def f8 { println("jack")}// f8() // 报错f8// 9. 如果不关心名称,只关心逻辑处理,那么def和函数名可以省略,即匿名函数或 lambda表达式(name: String) => { println(name) }}
}
二、函数高级
1. 匿名函数
1.1 介绍
-
概念:匿名函数是没有名字的函数,也称为 lambda 表达式
-
语法:
(param1: type1, param2: type2,...) => { statement }
1.2 案例
object TestLambda {def main(args: Array[String]): Unit = {// 单参数案例:val fun = (name: String) => { println(name) }fun("jack")def f(func: String => Unit): Unit = {func("jack")}f((name: String) => { println(name) })// 简化:// 1. 参数的类型可以省略,会根据形参进行自动的推导f((name) => { println(name) })// 2. 类型省略之后,若只有一个参数,则圆括号可以省略,否则不能省略圆括号f( name => { println(name) } )// 3. 匿名函数如果只有一行,则大括号也可以省略f( name => println(name) )// 4. 如果参数只出现一次,则参数省略且后面参数可以用_代替f( println(_) )// 5. 如果方法体只是某个方法的调用,则只需要传入调用的方法名f( println )// 多参数案例:def f2(func: (Int, Int) => Int): Int = {func(1,2)}f2((a: Int, b: Int) => a + b)f2((a: Int, b: Int) => a - b)f2((a, b) => a + b)f2((a, b) => a - b)f2( _ + _ )f2( _ - _ )f2((a, b) => b - a)f2( -_ + _ )}
}
1.3 练习
/**
练习 1:定义一个匿名函数,并将它作为值赋给变量 fun。函数有三个参数,类型分别为 Int,String,Char,返回值类型为 Boolean。要求调用函数 fun(0, “”, ‘0’) 得到返回值为 false,其它情况均返回 true。
练习 2:定义一个函数 func,它接收一个 Int 类型的参数,返回一个函数(记作 f1)。它返回的函数 f1,接收一个 String 类型的参数,同样返回一个函数(记作 f2)。函数 f2 接收一个 Char 类型的参数,返回一个 Boolean 的值。要求调用函数 func(0) (“”) (‘0’)得到返回值为 false,其它情况均返回 true
*/
object Practice {def main(args: Array[String]): Unit = {// 练习 1val fun = (a: Int, b: String, c: Char) => { if(a == 0 && b == "" && c == '0') false else true }println(fun(0, "", '0'))println(fun(1, "", '1'))println("==========================")// 练习 2def func(a: Int): String => (Char => Boolean) = {def f1(s: String): Char => Boolean = {def f2(c: Char): Boolean = {if(a == 0 && s == "" && c == '0') false else true}f2 _}f1}println(func(0)("")('0'))println(func(1)("")('1'))// 练习 2 匿名函数写法def func1(a: Int): String => (Char => Boolean) = {s => c => if(a == 0 && s == "" && c == '0') false else true}println(func1(0)("")('0'))println(func1(1)("")('1'))// 练习 2 柯里化写法(推荐)def func2(a: Int)(s: String)(c: Char): Boolean = {if(a == 0 && s == "" && c == '0') false else true}println(func2(0)("")('0'))println(func2(1)("")('1'))}
}
2. 高阶函数
2.1 特性
-
函数可以作为值进行传递
object TestHighLevelFunc {def main(args: Array[String]): Unit = {def f(n: Int): Int = {println("f调用")n + 1}def ff(): Int = {println("ff调用")1}val r1 = f(11) // 返回值val f1 = f _ // 函数 f 整体赋值给函数 f1val f11: Int => Int = f // 函数 f 整体赋值给函数 f1val r2 = ff // 返回值val ff1 = ff _ // 函数 ff 整体赋值给函数 ff1val ff2: () => Int = ff // 函数 ff 整体赋值给函数 ff2} }
-
函数可以作为参数进行传递
object TestHighLevelFunc {def main(args: Array[String]): Unit = {def calculator(func: (Int, Int) => Int, a: Int, b: Int): Int = {func(a, b)}def add(a: Int, b: Int): Int = {a + b}val result = calculator(add, 12, 18)println(result)// 匿名函数println(calculator((a,b)=>a + b, 12, 18))println(calculator(_ + _, 12, 18))} }
-
函数可以作为函数返回值返回
object TestHighLevelFunc {def main(args: Array[String]): Unit = {def outF(): Int => Unit = {def inF(a: Int): Unit = {println("inF调用 " + a)}// inF _inF // 将函数整体作为返回值返回}val result = outF() // inF 函数的引用地址值println(outF()(10)) // 最终函数调用} }
2.2 案例
object FunctionPractice {def main(args: Array[String]): Unit = {val array: Array[Int] = Array(1, 2, 3, 4)// 1. 模拟 Map 映射def mapOP(array: Array[Int], op: Int => Int): Array[Int] = {for(elem <- array) yield op(elem)}def add(elem: Int): Int = {elem + 1}val res1 = mapOP(array, add)val res2 = mapOP(array, _ + 1) // 匿名函数传参println(res1.mkString(","))println(res2.mkString(","))// 2. 模拟 Filter 过滤def filterOP(array: Array[Int], op: Int => Boolean): Array[Int] = {for(elem <- array if op(elem)) yield elem}def filterFun(elem: Int): Boolean = {if(elem % 2 == 0) true else false}val res3 = filterOP(array, filterFun)val res4 = filterOP(array, _ % 2 == 0) // 匿名函数传参println(res3.mkString(","))println(res4.mkString(","))// 3. 模拟 Reduce 聚合def reduceOP(array: Array[Int], op: (Int, Int) => Int): Int = {var init: Int = array(0)for (index <- 1 until arr.length) {init = op(init, arr(index))}init}def multiply(a: Int, b: Int): Int = {a * b}val res5 = reduceOP(array, multiply)val res6 = reduceOP(array, _ * _)println(res5)println(res6)}
}
3. 闭包
如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包
object TestClosure {def main(args: Array[String]): Unit = {def func(a: Int): String => (Char => Boolean) = {def f1(s: String): Char => Boolean = {def f2(c: Char): Boolean = {if(a == 0 && s == "" && c == '0') false else true}f2 _}f1} }
}/*在正常情况下,上述嵌套函数在调用的过程中由于外层函数先调用进行压栈和弹栈,所以外层函数的局部变量随着栈空间释放掉,从而导致内层函数调用时无法再获取到外层函数的局部变量,导致执行失败而 scala 的闭包是将外层函数和内层函数整体打包成一个对象存储在堆内存中,从而内层函数在调用时可以从线程共享的堆内存中获取到外层的局部变量正常执行
*/
4. 函数柯里化
-
概念:把一个参数列表的多个参数,变成多个参数列表
-
案例:
object TestCurrying {def main(args: Array[String]): Unit = {// 闭包def add(a: Int): Int => Int = {def f(b: Int): Int = {a + b}f}// 匿名函数简化def add1(a: Int): Int => Int = a + _// 柯里化可以简化闭包写法def add2(a: Int)(b: Int): Int = a + b} }
5. 递归
-
概念:一个函数/方法在函数/方法体内又调用了本身,称为递归调用
-
条件:
- 方法调用自身
- 方法必须要有跳出的逻辑
- 方法调用自身时,传递的参数应该有规律
- Scala 中的递归必须声明函数返回值类型
-
案例:
object TestRecursion {def main(args: Array[String]): Unit = {// 计算阶乘/* 普通递归实现缺点:在调用自身的同时前一次的调用必须等待(不能弹栈),从而导致大量占用Stack空间,进而引发 stack over flow*/def fact(n: Int): Int = {if(n == 0) 1 else fact(n - 1) * n}println(fact(5))/*尾递归优化实现函数式编程语言都是支持尾递归Scala 的注解 @tailrec 标注在方法上可以检验方法尾递归实现是否正确*/def tailFact(n: Int): Int = {@tailrecdef loop(n: Int, currRes: Int): Int = {if(n == 0) currRes else loop(n - 1, currRes * n)}loop(n, 1)}println(tailFact(5))} }
6. 控制抽象
Java 只有值调用;Scala 既有值调用,又有名调用
object TestControlAbstract {def main(args: Array[String]): Unit = {// 1. 值调用:把函数计算后的值作为参数传递给另一个函数def f0(a: Int): Unit = {println("a: " + a)println("a: " + a)}def f(): Int = {println("f被调用")10}f0(f()) // f 被调用一次println("==========================")// 2. 名调用:将代码块作为参数传递给一个函数// => Int 表示返回值类型为 Int 的代码块 def f1(a: => Int): Int = {println("a: " + a)println("a: " + a)}f1(f()) // f 被调用两次}
}
/*自定义 while 循环
*/
object MyWhile {def main(args: Array[String]): Unit = {// 1. 普通 while 循环var n = 10while(n >= 1) {println(n)n -= 1}// 2. 使用匿名函数、柯里化、闭包、控制抽象和递归实现自定义 while// 解析 while 语法:相当于 func(param1)(param2),param1 和 param2 都是代码块参数def myWhile(condition: => Boolean)(op: => Unit): Unit = {if(condition) {opmyWhile(condition)(op)}}var t = 10myWhile(t >= 1){ // 小括号可以省略println(t)t -= 1}}
}
7. 惰性加载
懒加载
-
概念:当接收函数返回值的变量 (val) 被 lazy 修饰时,函数的执行将被推迟,直到首次使用该变量时函数才会执行。这样的函数也称为惰性函数。
-
案例:
object TestLazyLoad {def main(args: Array[String]): Unit = {// 正常加载val n1: Int = sum(10, 12)println("1. 函数调用")println("2. result: " + n1)println("4. result: " + n1)// 打印顺序:3 > 1 > 2 > 4println("========================")// 惰性加载// lazy 不能修饰 var 类型的变量lazy val n2: Int = sum(11, 12)println("1. 函数调用")println("2. result: " + n2)println("4. result: " + n2)// 打印顺序:1 > 3 > 2 > 4}def sum(a: Int, b: Int): Int = {println("3. sum 调用")a + b}}