文章目录
- 在之前Java课程的学习中,我们一直学习的就是面向对象编程,所以解决问题都是按照面向对象的方式来处理的。比如用户登陆等业务功能,但是接下来,我们会学习函数式编程,采用函数式编程的思路来解决问题。scala编程语言将函数式编程和面向对象编程完美地融合在一起了。
- 面向对象编程
分解对象,行为,属性,然后通过对象的关系以及行为的调用来解决问题 - 函数式编程
将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的功能按照指定的步骤,解决问题。
⌘ 基础函数编程
– 基本语法
[修饰符] def 函数名 ( 参数列表 ) [:返回值类型] = {
函数体
}
- 一般是返回值类型不写
private def test( s : String ) : Unit = {
println(s)
}
– 函数&方法
- 方法概念来自于Java,函数概念来自于Scala
- Scala又是完全函数式编程语言。所以方法也都可以看做函数,可以赋值给一个变量。一般情况下,可以理解为方法是组成类的一部分,函数则是一个完整的对象。
Scala中的方法和函数从语法概念上来讲,一般不好区分,所以简单的理解就是:方法也是函数。只不过类中声明的函数称之为方法,其他场合声明的就是函数了。类中的方法是有重载和重写的。而函数可就没有重载和重写的概念了,但是函数可以嵌套声明使用,在类中的方法不能嵌套声明。
– 函数定义
- 无参,无返回值
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun1(): Unit = {
println("函数体")
}
fun1()
}
}
- 无参,有返回值
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun2(): String = {
"zhangsan"
}
println( fun2() )
}
}
- 有参,无返回值
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun3( name:String ): Unit = {
println( name )
}
fun3("zhangsan")
}
}
- 有参,有返回值
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun4(name:String): String = {
"Hello " + name
}
println( fun4("zhangsan") )
}
}
- 多参,无返回值
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun5(hello:String, name:String): Unit = {
println( hello + " " + name )
}
fun5("Hello", "zhangsan")
}
}
- 多参,有返回值
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun6(hello:String, name:String): String = {
hello + " " + name
}
println( fun6("Hello", "zhangsan"))
}
}
– 函数参数
— 函数参数的个数
- 最大个数为22,声明的时候可以超过22,但是如果超过22个将函数作为对象赋值给变量时会报错
— 可变参数
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun7(names:String*): Unit = {
println(names)
}
fun7()
fun7( "zhangsan" )
fun7( "zhangsan", "lisi" )
}
}
- 可变参数不能放置在参数列表的前面,一般放置在参数列表的最后
oobject ScalaFunction {
def main(args: Array[String]): Unit = {
// Error
//def fun77(names:String*, name:String): Unit = {
//}
def fun777( name:String, names:String* ): Unit = {
println( name )
println( names )
}
}
}
— 参数默认值,
- Scala中函数的参数使用val声明,无法进行修改,Scala提供了参数默认值的语法来解决参数默认值的问题。
- 参数默认(初始)值: 参数声明时进行初始化
- 如果函数存在默认值的参数,调用时可以不用传递,不传参数便会使用默认值
- 如果调用函数时,提供了参数值,那么参数默认值不起作用,被覆盖了
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun8( name:String, password:String = "000000" ): Unit = {
println( name + "," + password )
}
fun8("zhangsan", "123123")
fun8("zhangsan")
}
}
— 带名参数
- 带名参数 : 传递参数时添加参数名,明确指定参数赋值
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun9( password:String = "000000", name:String ): Unit = {
println( name + "," + password )
}
fun9("123123", "zhangsan" )
fun9(name="zhangsan")
}
}
– 函数至简原则
- 所谓的至简原则,其实就是Scala的作者为了开发人员能够大幅度提高开发效率。通过编译器的动态判定功能,帮助我们将函数声明中能简化的地方全部都进行了简化。也就是说将函数声明中那些能省的地方全部都省掉。所以这里的至简原则,简单来说就是:编译器可以自动推断的场合,能省则省。
— 省略return关键字
- 当函数需要返回值时,将函数体中最后一行执行的代码作为返回结果,所以可以省略return关键字
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun1(): String = {
return "zhangsan"
}
def fun11(): String = {
"zhangsan"
}
}
}
— 省略花括号
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun2(): String = "zhangsan"
}
}
— 省略返回值类型
-
不一定能省,调用的时候()可加可不加
-
如果编译器可以推断出函数的返回值类型,那么返回值类型可以省略
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun3() = "zhangsan"//编译器会自动推断,要求会看懂!
}
}
— 省略参数列表
-
调用时()也不能加
-
如果函数没有提供参数,那么声明时,小括号也可以省略, 省略后调用时,也不能使用小括号
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun4 = "zhangsan"
}
}
— 省略等号
- 如果函数体中有明确的return语句,那么返回值类型不能省略
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun5(): String = {
return "zhangsan"
}
println(fun5())
}
}
- 如果函数体返回值类型明确为Unit, 那么函数体中即使有return关键字也不起作用,白写
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun5(): Unit = {
return "zhangsan"
}
println(fun5())
}
}
- 如果函数体返回值类型声明为Unit, 但是又想省略,那么此时就必须连同等号一起省略,省略了返回值则肯定为Unit(无返回值),这种函数的声明方式称之为过程函数, 不省略花括号
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun5() {
return "zhangsan"
}
println(fun5())
}
}
object Scala04_Function3 {
def main(args: Array[String]): Unit = {
test() //xxxxx
test //xxxxx
test _
test1 //yyyyy
//test1()方法定义时已省略括号,则调用时不能加()
test1 _
}
def test(): Unit = {
println("xxxxx")
}
def test1 {
println("yyyyy")
}
}
— 省略名称和关键字
- 当只关心代码逻辑,不关心函数名称时,函数名和def关键字可以省略,将没有名称和def关键字的函数称之为匿名函数
匿名函数规则:(参数列表) => { 代码逻辑 } - =>常表示方法
object ScalaFunction {
def main(args: Array[String]): Unit = {
() => println("zhangsan")
}
}
// 声明匿名函数
val a = (i:String) => println(i)
// 调用
a("匿名函数被调用")//匿名函数被调用
⌘ 高阶函数编程
- 所谓的高阶函数,其实就是将函数当成一个类型来使用,而不是当成特定的语法结构。
– 函数作为值
- 将函数赋值给变量,那么这个变量其实就是函数,可以调用
- 函数如果没有参数列表,那么调用时可以省略小括号
- 如果此时希望将函数不执行,而是当成一个整体赋值给变量,那么需要使用下划线
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun1(): String = {
"zhangsan"
}
val a = fun1
val b = fun1 _
println(a)//不加_,编译器会以为执行这个函数并赋值,输出zhangsan!!!
println(b)
}
}
重要!!!
val ff1 = test11 _等于
val ff1:(Int)=>Int =test1 等于
val ff1:Int=>Int =test1 如果只有一个输入参数,则()可省略
(Int)=>Int即表示函数类型,标明函数的输入输出类型
val b = fun1 _等于
val b :()=>String= fun1
– 函数作为参数
- 函数可以作为参数传递给其他的函数
- 函数名( 参数名1 :函数类型)
- 将函数作为参数使用时,一般不关心函数的名称,所以一般使用匿名函数
- 函数名((参数列表) => { 代码逻辑 })
def fun2( i:Int ): Int = {
i * 2
}
//( f : Int => Int )表示传一个输入为Int输出也为Int的函数
def fun22( f : Int => Int ): Int = {
f(10)
}
//不用加下划线是fun22指定了参数为函数,能确定是函数了,加下划线也是为了确定是函数
println(fun22(fun2))//20
即:
def fun2( i:Int ): Int = {
i * 2
}
def fun22( f : Int => Int ): Int = {
f(10)
}
//将函数赋值给不可变变量
val f222 = fun2 _
println(fun22(f222))//20
- ( f : (Int) => Int )表示传一个输入为Int输出也为Int的函数,就是一个函数的类型,就像String一样,如果只有一个输入参数,则()可省略,所以 Int => Int
// TODO 至简原则
//val result = test2( (i:Int) => {i * 2} )
// 如果逻辑代码只有一行,大括号可以省略
//val result = test2( (i:Int) => i * 2 )
// 如果匿名函数的参数类型可以推断出来,那么类型可以省略
//val result = test2( (i) => i * 2 )
// 如果匿名函数的参数列表只有一个或没有,那么小括号可以省略
//val result = test2( i => i * 2 )
// 如果匿名函数中的参数在逻辑代码中只使用了一次,那么,参数和=>可以省略
// 使用下划线代替参数
val result = test2( _ * 2 )
println(result)
– 函数作为返回值
- 函数可以作为函数的返回值返回,当函数作为返回值使用时,一般会使用嵌套函数
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun3( i:Int ): Int = {
i * 2
}
def fun33( ) = {
fun3 _
}
println(fun33()(10))//20,fun33()返回fun3 _
}
}
def main(args: Array[String]): Unit = {
def test(i:Int)={
def sum(j:Int)={
i+j
}
sum _
}
println(test(10)(20))//30,test(10)返回sum _,且闭包使i成为sum方法的参数
}
}
- 问: test方法都执行完失效了,i已经回收了,为什么还能用在sum方法中能用?
- 函数在使用外部变量时,如果外部变量失效时,会将这个变量包含到当前的函数内部,形成闭合的使用效果,改变变量的生命周期,讲这种操作称之为闭包。
– 匿名函数
- 将两数作为参数使用时,一般不关心函数的名称,所以一般使用匿名函数
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun4( f:Int => Int ): Int = {
f(10)
}
println(fun4((x:Int)=>{x * 20}))
//如果匿名函数的参数类型可以推断出来,那么类型可以省略
println(fun4((x)=>{x * 20}))
//如果逻辑代码只有一行,大括号可以省略,重要!!多行复杂不能省而且是{}
println(fun4((x)=>x * 20))
//如果匿名函数的参数列表只有一个或没有, 那么小括号可以省略
println(fun4(x=>x * 20))
//如果匿名函数中的参数在逻辑代码中只使用了一次,那么,
//参数和=>可以省略,使用下划线代替参数
println(fun4(_ * 20))
}
}
-
如果匿名函数的参数在代码中只使用了一次,那么,参数和=>可以省略,使用下划线代替参数
-
原则:编译时能推断出来的东西才能省,不然就不是静态语言了
-
经典练习:
如果设计一个用于过滤的函数,你会如何做?
要求:对传递的包含多个单词的字符串参数,根据指定的规则对word进行过滤
例子:“hello world scala spark” => 首写字母为s => “scala, spark”
def +filter(s:String,fun:(String)=>Boolean):String={
val words :Array[String]= s.split(" ")
var result=""
for(word <- words){
if(fun(word))
result += "," + word
}
result.substring(2)
}
filter("hello world scala spark",(word:String)=>{word.startsWith("s")})
filter("hello world scala spark",_.startsWith("s"))
– 闭包
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun5() = {
val i = 20
def fun55() = {
i * 2
}
fun55 _
}
fun5()()
}
}
-
内部函数用到了外部变量,会有闭包效果。匿名函数肯定为闭包,将函数赋值给变量使用也是闭包,嵌套函数的使用(使用内部函数)会有闭包。
-
反编译后发现编译器重新声明了内部函数的参数,将外部变量在内部函数参数列表进行重新声明使用。在早起的Scala版本(2.11),闭包是会编译成匿名函数类
-
在早些版本:闭包会被编译为匿名函数类,如果使用外部变量,会将外部变量作为类的属性。
-
在早些版本:即使没有使用外部的变量,也会有闭包的效果,只是没有包含外部变量。
在spark怎么判断闭包:判断类名中是否有匿名内部类 -
没有使用外部变量还能称之为闭包吗?
-
也是有可能的,只要改变了变量的生命周期就是闭包,编译后会产生一个匿名内部类,后面编译器升级了就没有用匿名内部类的方式了。
– 函数柯里化
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun6(i:Int)(j:Int) = {
i * j
}
}
}
def test2(i:Int)(j:Int)(f:(Int,Int)=>Int):Int={
f(i,j)
}
println(test2(1)(2)(_+_))
}
看到两个独立的括号就考虑是函数柯里化
– 控制抽象
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun7(op: => Unit) = {
op
}
fun7{
println("xx")
}
}
}
- op: => Unit没有(),代码也可以当成参数传进来,此处传的println(“xx”)代码。控制抽象可以用来封装固定代码,传业务代码,实现框架时用的非常多。
– 递归函数
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun8(j:Int):Int = {
if ( j <= 1 ) {
1
} else {
j * fun8(j-1)
}
}
println(fun8(5))
}
}
1.方法执行过程中调用自身
2.存在可以跳出递归的逻辑,可能会出现Stack0verflowError(栈溢出)
3.方法调用时,传递的参数之间应该存在规律。
4.尾递归压一个弹一个,编译会优化成while循环,但是如果压栈速度远大于出栈速度也会出现OOM
– 惰性函数
- 当函数返回值被声明为lazy时,函数的执行将被推迟,直到我们首次对此取值,用上了该函数时才会执行。这种函数我们称之为惰性函数。
object ScalaFunction {
def main(args: Array[String]): Unit = {
def fun9(): String = {
println("function...")
"zhangsan"
}
lazy val a = fun9()
println("----------")
println(a)
}
}
-
函数到底是什么?实现原理?
-
Scala都是对象,函数也是对象,类也是函数(构造函数)