7 函数式编程
main()方法的返回值类型Unit也可以省略,但前提是自动推断的类型一定要是Unit,不然main方法没有入口
object TestClosePackage2 {
def main(args: Array[String]): Unit = {
val name = "zhangSan"
def test() = {
println(name)
}
val f = test _ //返回函数的这个对象,调用的时候使用f()
val f2 = test() //返回函数的返回值,同时调用
val f3 = test //和test()一样,只是省略了()
println(f)
println(f2)
println(f3)
def fun1() = {
println("fun1....")
"zhang san"
}
// val f1 = fun1() //调用函数fun1()的同时,将返回值给f1
// val f1 = fun1 //调用函数fun1的同时,将返回值给f1
val f1 = fun1 _ //将函数fun1作为对象赋给f1,调用的时候使用f()
}
}
7.1 基础函数式编程
[修饰符] def 函数名 ( 参数列表 ) [:返回值类型] = {
函数体
}
private def test( s : String ) : Unit = {
println(s)
}
函数 & 方法
Java中没有函数的概念,有方法的概念
Scala:在类中声明的函数就称为方法,其他地方声明的就是函数。
因为方法声明在类中,所以就要符合方法的约束和规则;方法可以重载和重写。
方法可以重载和重写,函数没有!
object TestFun1 {
def main(args: Array[String]): Unit = {
println(test2())
//函数式编程:
//1 无参无返回值
def test1(): Unit = {
val name = "Tom"
val age = 23
println(s"this is test 1 ${name} and this is test1 ${age}")
}
//2 无参有返回值
def test2(): String = {
return "this is function2"
}
//3 有参无返回值
def test3(username: String, age: Int): Unit = {
val username1 = username
val age1 : Int = age
println(
s"""
|your name is ${username1}
|your age is ${age1}
|""".stripMargin)
}
//4 有参有返回值
def test4(name :String, age:Int, money :Double) : Double = {
if (name.equals("Tom")){
return age * money
}
return money - age
}
// def test4(count :Int) :Unit = {
//
// }
}
//方法可以重载;凡是函数不能重载
def method1(gender :Char) :Boolean = {
if(gender == 'M'){
return true
}
return false
}
def method1(gender:Char, money :Double): Unit ={
if (gender == 'M'){
printf("money is :%s\n", money)
}
}
}
7.2 函数参数
①可变参数
- 可变参数加上*号;类似于Java的…
- 可变参数不能放在参数列表的前面,一般放在参数列表的最后
object Test3 {
def main(args: Array[String]): Unit = {
//1 可变参数
def fun1(name: String): Unit = {
println(name)
}
// fun1() //错
fun1("Tom")
// fun1("Tom", "Jerry") //对
def fun2(name: String*): Unit = {
fun2()
fun2("Tom")
fun2("Tom", "Jerry")
}
//可变参数不能放置在参数列表的前面,一般放在参数列表的最后
//错误的:
// def fun3(name : String*, age : Int) :Unit = {
// }
//正确的:
def fun4(age: Int, name: String*): Unit = {
println(age)
println(name)
}
}
}
-
函数的参数有多少个呢?
函数的参数可以有无限多,但是将函数给其他人使用,这个时候参数的个数有限制,不能多于22个。
②参数默认值
object Test3 {
def main(args: Array[String]): Unit = {
//给参数设置默认值
def fun6(password: String = "123456"): Unit = {
println(password)
}
fun6(null) //null
//设置了默认值那就让参数列表为空就好了
fun6() //123456
//当带有默认值的参数放在参数列表的前面,
def fun7(password: String = "123456", user: String): Unit = {
println(password + ", " + user)
}
//①调用函数的时候,不能省略有默认值的参数
fun7("abc", "Tom") //abc, Tom
//当带默认值的参数放在了参数列表的后面。
def fun8(username: String, password: String = "ROOT"): Unit = {
println(username + "=== " + password)
}
//②调用函数的时候,可以声明后面带有默认值的参数
fun8("Jerry")
}
}
③ 带名参数
按照指定名称把参数放到指定的顺序位置
object ScalaFunction {
def main(args: Array[String]): Unit = {
//带名参数
def fun9(name :String = "Tom", school : String) : Unit = {
println(name + "--==" + school)
}
fun9(school = "bigdata")
}
}
7.3 函数至简原则
所以这里的至简原则,简单来说就是:能省则省。
①省略return关键字
函数可以将最后一行代码的结果作为函数的返回值
//1 省略return关键字
//TODO 1 函数可以将最后一行代码的结果作为函数的返回值
def fun1(): String = {
"zhang san"
}
val name = fun1()
println(name)
②省略返回值类型
如果返回值类型能够确定,那么类型可以省略
//TODO 2 如果返回值能够确定,那么类型也可以省略
def fun2() = {
"zhang san"
}
def fun3() = {
var age = 30
if (age < 20)
10
else
"string"
}
省略返回值类型的话,看最后一行的类型,如果最后一行不能判断是什么类型,那就是他的父类型。
③ 省略大括号
如果函数逻辑代码只有一行,那么大括号可以省略
//TODO 3 如果函数逻辑代码只有一行,那么大括号可以省略
def fun4() = "zhang san"
def fun5() = println("li si")
④省略小括号
如果参数列表没有参数,那么小括号可以省略
//TODO 4 如果参数列表没有参数,那么小括号可以省略
def fun6 = {
if (8 > 2)
"Bigger"
}
⑤省略等号
5.1 如果函数的返回值为Unit 那么可以和 = 号一起省略
但是需要注意,print(fun1)的结果是(); print(fun1 _)的结果是将函数作为一个对象返回,结果是地址
// def fun1(): String = {
// return "Tom and Jerry"
// }
def fun1() {"Tom and Jerry"}
5.2 如果函数体中明确有return语句,那么返回值类型不能省略
// def fun1(): String = {
// return "Tom and Jerry"
// }
def fun1(): String = {
return "Tom and Jerry"
}
5.3 如果函数的返回值类型是Unit,那么函数体内的return关键字不起作用
也就是说:print(fun1)的结果是()
// def fun1(): String = {
// return "Tom and Jerry"
// }
def fun1() :Unit = {
return "Tom and Jerry"
}
println(fun1) //()
⑥省略名称和关键字(匿名函数)
匿名函数,因为没有名字所以无法直接调用,需要赋值给其他的变量来调用
如果函数声明时省略了参数列表,那么调用时也不能加参数列表
// TODO 6 省略名称和关键字也就是:匿名函数
() => {
println("Tom")
}
//带参数的匿名函数
// (name :String) =>{
// println(name)
// }
// 把匿名函数的结果传给一个变量。
val result: String => Unit = (name: String) => {
println(name)
}
result("Tom") //Tom
val unit: () => Unit = () => {
println("Jerry....")
}
unit() //Jerry....
//这是因为匿名函数的返回值类型时Unit,函数体内的return不起作用
val function: String => Nothing = (name: String) => {
return name
}
function("Tom") //没有结果输出
- 直接写匿名函数:
object Test2 {
def main(args: Array[String]): Unit = {
// def fun1(): String = {
// return "Tom and Jerry"
// }
//TODO 1 省略return关键字
// def fun1(): String = {
// "Tom and Jerry"
// }
// TODO 2 如果能够确定函数的返回值类型,那么类型可以省
// def fun1() = {
// "Tom and Jerry"
// }
// TODO 3 如果函数的逻辑代码只有一行,那么可以省略大括号
// def fun1() = "Tom and Jerry"
// TODO 4 如果函数的参数列表没有参数可以省略小括号
// def fun1 = "Tom and Jerry"
// TODO 5 如果函数的返回值类型为Unit 那么可以和=号一起省略
//但是print(fun1)的结果是(); print(fun1 _)的结果是将函数作为一个对象返回,结果是地址
// def fun1() {"Tom and Jerry"}
// TODO 5.1 如果函数体中明确有return语句,那么返回值类型不能省略
def fun1(): String = {
return "Tom and Jerry"
}
// TODO 5.2 如果函数的返回值类型明确是Unit,那么函数体中即使有return关键字也不起作用。
//print(fun1)结果为()
// def fun1() :Unit = {
// return "Tom and Jerry"
// }
println(fun1)
}
}
7.4 高级函数编程
所谓的高阶函数,其实就是将函数当成一个类型来使用,而不是当成特定的语法结构。
① 函数作为值
object TestFunction_Hell {
def main(args: Array[String]): Unit = {
//TODO scala 万事万物皆对象,所以函数也是对象,所以可以给其他人使用
def fun1() : Unit = {
println("zhang san")
}
//将函数作为对象赋值给变量,这个变量其实就是函数
//函数有类型,类型的名字就是Function
//函数类型在Scala中默认识别22个参数,如果函数参数超过22,就无法使用
//TODO 注意点1!
val f1 = fun1() //调用fun1(),同时将结果赋值给f1,结果是()
// f1() //报错
println(f1) // ()
//TODO 注意点2!
val f11 : Function0[Unit] = fun1 //这是将函数作为一个对象赋值给一个变量
//上面的其实是完整的写法是下面
val f12 : ()=> Unit = fun1
f11() //调用函数要使用函数名+()
f12()
//TODO 注意点3!
//如果想要让函数作为对象类使用,但是又不想声明函数类型,那么可以采用特殊符号
val f3 = fun1 _
f3()
}
}
② 函数作为参数
将函数作为参数来使用一般使用匿名函数
object TestSeniorFun2 {
def main(args: Array[String]): Unit = {
// TODO 将函数作为参数来使用
// 一般情况使用匿名函数
def fun1(f : ()=> Unit) : Unit = {
f()
}
def test() = {
println("Test....")
}
//方式1:直接将test()这个函数传给,fun1
// fun1(test)
//方式2:将test()这个函数赋值给一个变量,将变量传给fun1
val f : ()=> Unit = test
fun1(f)
// TODO 使用匿名函数
def fun2(f: (Int, Int) => Int) = {
f(10, 20)
}
// def test2(i: Int, j:Int) = {
// i * j
// }
// def test2(i: Int, j: Int) = {
// i + j
// }
//
// val result = fun2(test2)
//可以直接定义一个匿名函数作为参数传到fun2中
val result = fun2((i:Int, j: Int) => {i + j})
println(result)
}
}
③ 函数作为返回值
object TestSeniorFun33 {
def main(args: Array[String]): Unit = {
def fun(i: Int) : Int = {
i * 2
}
//TODO: 将函数作为返回值使用
def fun2() = {
fun _
}
println(fun2()(10))
}
}
噩梦难度:
object TestFunction_Hell_6 {
def main(args: Array[String]): Unit = {
//TODO 将函数作为返回值返回
def fun(): Unit = {
println("fun....")
}
def test() = {
fun //把函数的结果返回 这样test()的类型就是Unit
// fun _ //把函数当作整体返回,这样test()的类型就是()=>Unit
}
//如何调用呢?
// val unit = test()
// unit()
// val test1 = test
// test1()
// test()()
test()
//TODO 将函数作为返回值返回
// 一般的应用场合为将内部的逻辑给外部使用
//如果将函数返回,那么一般省略返回值类型,应用自动类型推断
def outer() = {
println("outer...")
def inner() = {
println("inner...")
}
inner _
}
outer()()
//oper(10)(10)(_+_) = 20
//oper(10)(10)(_*_) = 100
//oper(10, 10, _+_) = 20
def oper(x: Int) = {
def middle(y: Int) = {
def inner(f: (Int, Int) => Int): Int = {
f(x, y)
}
inner _
}
middle _
}
println(oper(10)(20)((x: Int, y: Int) => x + y)) //30
println(oper(10)(20)(_ + _)) //30
}
}
④ 匿名函数
object TestSeniorFun3 {
def main(args: Array[String]): Unit = {
def fun(f : (String) => Unit) : Unit = {
f("zhang san ")
}
def test(name :String) : Unit = {
println(name)
}
// fun(test)
//TODO :使用匿名函数
// fun((name:String) => {println(name)}) //zhang san
//如果匿名函数的逻辑代码只有一行,那么大括号可以省略
// fun((name : String) => println(name))
//如果参数列表的类型可以推断出来,那么参数类型可以省略
// fun((name) => println(name))
//如果参数列表中的参数只有一个 ,那么小括号可以省略
// fun(name => println(name))
//如果在函数体中,参数只按照顺序使用了一次,那么参数就可以省略
//使用下划线代替这个参数
fun(println(_))
}
}
object TestLazyFunction {
def main(args: Array[String]): Unit = {
def fun(f:(String, Int) => Unit) = {
f("Tom", 23)
}
//匿名函数
fun((x : String, y: Int) => {println(s"name=${x}, age=${y}")})
}
}
⑤ 地狱般难度-补充
object Test3 {
def main(args: Array[String]): Unit = {
def fun(a: Int, b: Int, f: (Int, Int) => Int) = {
val result = f(a, b)
println(result)
}
fun(10, 20, (i: Int, j: Int) => {
i + j
}) //30
fun(10, 20, (i: Int, j: Int) => i+j) //30
fun(10, 20, (i, j) => i + j) //30
fun(10, 20, _+_) //30
def test(a : Int, b: Int, f: (Int, Int) => Int) = {
val result = f(a, b)
println(result)
}
// test(10, 20, (i: Int, j: Int) => {i - j})
test(10, 20, _-_)//-10,但是这个时候i和j的顺序就不能变,两个下划线按照顺序依次表示i和j
}
}
⑥ 闭包
问题来了:oper(10)方法执行完了,方法就弹出栈,那么r1(20)中的x+y的x呢?
scala2.12之前采用的是匿名函数类实现闭包效果
spark3.0框架判断闭包的代码就发生了该百年
//TODO 一个函数使用了外部的变量,然后改变了这个变量的生命周期的操作 // 这样的操作称之为“闭包”,inner本来没有x=10(因为已经弹出栈了)但是为了能够使用,把它放在 // inenr()方法的内部,形成一个闭合的环境,称为闭包环境。 //将不能直接使用的变量包含到当前函数的内部,改变生命周期,形成闭合的环境,称为闭包环境 //TODO 判断是否存在闭包 //1 函数使用的变量有没有改变生命周期 //2 函数有没有当成对象给别人使用 //3 所有的匿名函数都有闭包。因为匿名函数都是给别人使用的。
object TestClosePackage {
def main(args: Array[String]): Unit = {
def oper(x: Int) = {
def inner(y: Int) = {
x + y
}
inner _
}
// println(oper(10)(20))
//TODO 一个函数使用了外部的变量,然后改变了这个变量的生命周期的操作
// 这样的操作称之为“闭包”,inner本来没有x=10(因为已经弹出栈了)但是为了能够使用,把它放在
// inenr()方法的内部,形成一个闭合的环境,称为闭包环境。
//将不能直接使用的变量包含到当前函数的内部,改变生命周期,形成闭合的环境,称为闭包环境
//也可以写成这样:
val r1 = oper(10)
val r2 = r1(20)
println(r2)
}
}
判断存不存在闭包?
object TestClosePackage2 {
def main(args: Array[String]): Unit = {
val name = "zhangSan"
def test() = {
println(name)
}
// test()//这样就没有闭包
val f = test _
f() //这样就有闭包
//TODO 判断是否存在闭包
//1 函数使用的变量有没有改变生命周期
//2 函数有没有当成对象给别人使用
//3 所有的匿名函数都有闭包。因为匿名函数都是给别人使用的。
}
}
思考问题?没有使用外部变量还能称为闭包码?
是有可能的,没有改变别人的生命周期,有可能改变自己的生命周期
⑦ 抽象控制
抽象控制:将代码作为参数进行传递,一般应用于框架,实现特殊语法。
Scala支持把一段逻辑传给别人来用
object TestControlAbstract {
def main(args: Array[String]): Unit = {
//这样是给oper函数传进入一个函数
def oper(f : (Int) => Unit) = {
f(10)
}
//这样是给oper1函数传进入一段代码;这就是控制抽象
def oper1(f : => Unit) = {
f
}
//那么怎么去调用控制抽象的这个函数呢?
oper1(
println("This is a control abstract")
)
//也可以用大括号:
oper1{
for(i <- Range(0, 5)){
println(i)
}
}
}
}
⑧ 柯里化
柯里化:其实就是将复杂的多个参数简单化,==> 声明为多个参数列表
柯里化,用一句话解释就是,把一个多参数的函数转化为单参数函数的方法。柯里化收的函数是分步执行的,第一次调用返回的是一个函数,第二次调用的时候才会进行计算。起到延时计算的作用,通过延时计算求值,称之为惰性求值。
object TestKeLi {
def main(args: Array[String]): Unit = {
//TODO 函数柯里化
val i = 10 //10min
val j = 20 //20min
def test(i: Int, j: Int) = {
for (k <- 1 to i) {
println(k) //10min
}
for (m <- i to j) {
println(m) //20min
}
}
//i和j完全独立没有任何关系
test(i, j) //60min
//将复杂的参数简单化,声明多个参数列表,这种表达形式就是柯里化
def test1(i: Int)(j:Int) = {
}
test1(i)(j) //50min
}
}
⑨ 惰性函数
当声明为lazy的函数,函数的执行将推迟,直到我们首次对此取值,该函数才会执行。这种函数称为惰性函数。
就比如, val f1 = fun1()如果fun1返回的是10000个对象,但是在逻辑的最后一行才用到了f1这个变量,那么在使用之前要一直占用了10000个对象的内存,就很占用资源。所以才出现了惰性函数。
object TestLazyFunction {
def main(args: Array[String]): Unit = {
def fun1() = {
println("fun1....")
"zhang san"
}
//调用函数fun1()的同时,将返回值给f1
val f1 = fun1() //1首先调用fun1(),打印fun1....
println(f1) //2 然后将fun1()的return结果赋给f1变量,打印“zhang san”
//延迟加载(分页查询)
lazy val f1 = fun1() //使用lazy修饰的函数,将会推迟执行,直到
}
}
⑩ 递归
-
递归:函数内部调用自身
-
函数应该存在跳出递归的逻辑
有跳出的逻辑,也有可能出现栈溢出,栈内存中的小格子不够了
-
函数在传递参数时应该有规律
-
递归的方法必须明确声明返回值类型,不能省略!
object TestRecursion {
def main(args: Array[String]): Unit = {
// TODO 递归函数
// 1 函数内部调用自身
// 2 函数应该存在跳出递归的逻辑
// 3 函数在传递参数时应该有规律
// 4 递归的方法必须明确声明返回值类型,能不能省略
//因为能省返回值类型的前提是编译器能够推断出类型,但是如果不写的话,递归调用的test因为
//省略了类型,所以它不知道是什么类型!
//有跳出的逻辑,也有可能出现栈溢出,栈内存中的小格子不够了
println(test(5))
//求5的阶乘
def test(num : Int): Int = {
if (num <= 1)
1
else
num * test(num - 1)
}
}
}
尾递归
尾递归(伪递归)
递归,在调用的时候,因为方法没有执行完,会一直有方法压栈,当栈溢出了就会报StackOverflowError
尾递归,在调用的时候,方法其实已经执行完了,可以把执行完的方法先弹出去,这样就不会一直压栈而导致栈溢出,Scala编译器将尾递归编译成了while死循环
def main(args:Array[String]): Unit = {
//TODO 递归
def test() : Unit = {
test()
println("recursive....")
}
//TODO 尾(伪)递归
def test1 (): Unit = {
println("tail recursive....")
test1()
}
test()// 递归,栈溢出就会报错了
test1()// 尾递归,不会报错,scala会优化成while死循环
}
}
Scala编译器在编译的时候,会将尾递归编译成while死循环
java中的内存操作
-
StackOverflowError 栈滚动错误,栈溢出。栈溢出 != 栈内存溢出
-
栈溢出
栈有一个深度的概念每个方法会压栈,是以栈帧的方式存在栈内。如果栈是1M,栈帧1K的话,那么只能压1024个栈帧,如果是递归一直调用自己,超过1024个就会出现栈溢出!
-
栈内存溢出
什么时候会出现栈内存溢出?
Thread,java虚拟机会给每个线程分配一个栈内存,一个线程分配一个栈。
如果一个栈式5M,要是有1024个线程,就需要5个G的内存,Java虚拟机默认占用多少内存?
- 占可用内存的1/64;最大占可用内存的1/4
函数到底是什么?
- 函数在编译的时候,就是将这个方法加上了$+编号,并且声明为private static final
- 方法就是java中的成员方法
object TestWhatIsFunction {
def main(args: Array[String]): Unit = {
// TODO 函数到底是什么?
// 1 函数
//编译时,会编译为private static final方法,方法名和函数名不一样,增加了后缀$
//私有的不能在其他的类中调用。
// 2 方法
//编译时,会编译为java的成员方法
//成员方法可以在其他类中调用
def test()= {
println("test1....")
def test() = {
println("test2....")
}
}
test() //test1....
}
def test() = {
println("test3....")
}
}