【Scala---04】函数式编程 『 函数 vs 方法 | 函数至简原则 | 函数式编程』
object 类名 {def 方法名([变量:变量类型,变量:变量类型]):返回值类型 = {方法体比如:方法不能作为值传递x + yval result = add // 会报错方法不能作为参数传递// 1. 定义calculate方法def calculate(x: Int, y: Int, func: (Int, Int) => Int): Int = { // x与y进行func操作,这个
文章目录
1. 函数 vs 方法
在Java中 方法 与 函数 没区别,但是在Scala中 方法 和 函数 是不一样的。Java是面向对象的,Scala是面向函数式编程,也是面向对象编程的。主要体现在如下4点:
Java/Scala | 对象可以作为一个值传递 | 对象可以作为参数传递 | 对象可以作为返回值传递 | 可以调用对象 |
---|---|---|---|---|
Scala | 函数可以作为一个值传递 | 函数可以作为参数传递 | 函数可以作为返回值传递 | 可以调用函数 |
Scala | 方法不可以作为一个值传递 | 方法不可以作为参数传递 | 方法不可以作为返回值传递 | 可以调用函数 |
可以看出Scala中函数比方法更加强大,而函数相比于方法最重要的功能就是函数能作为参数传递,以及作为返回值传递,也就是说h = f(x, y, g(x, y))
中函数h = f(x, y, z)
通过z = g(x, y)
可变而可变,并不是写死的。
具体的区别如下:
- 从位置上理解:方法 只能在类中定义,做为类的属性;函数 可以在任何地方定义。
- 从是否可以重载的角度:方法定义在类中可以实现重载;函数不可以重载。
- 从运行位置角度:方法是保存在方法区;函数是保存在堆中,因为函数是系统内置的,总共有23个函数(一个无参 + 22个有参)。也就是说定义一个函数,最多只能定义22个形参。
1.1 方法
(1) 定义方法
定义方法语法如下:
class或object 类名 {
def 方法名([变量:变量类型,变量:变量类型]):返回值类型 = {
方法体
}
}
比如:
-
方法不能作为值传递
object Main { def main(args: Array[String]): Unit = { def add(x:Int, y:Int):Int = { x + y } val result = add // 会报错 println(result) } }
-
方法不能作为参数传递
object Main { def main(args: Array[String]): Unit = { // 1. 定义calculate方法 def calculate(x: Int, y: Int, func: (Int, Int) => Int): Int = { // x与y进行func操作,这个func函数需要指名 形参类型和返回值 func(x, y) } // 2. 定义add方法 def add(x:Int, y:Int):Int = { x + y } // 3. 函数作为参数传递 val result = calculate(2, 3, add) // 不会报错,自动转为函数传递 println(result) } }
-
方法不能作为返回值传递
object Main { def main(args: Array[String]): Unit = { // 1. 定义getFunc方法 def getFunc() = { // x与y进行func操作,这个func函数需要指名 形参类型和返回值 def func(x: Int, y: Int) = { x + y } func // 会报错 } // 2. 得到函数 println(getFunc()) } }
-
方法可以直接调用
object Main { def main(args: Array[String]): Unit = { def add(x: Int, y: Int) = x + y println(add(2, 3)) // 输出:5 } }
(2) 运算符即方法
- 所有的运算符本质上是方法。比如查看
Int
类
可以看到类中定义了很多以运算符命名的方法
因此,以下两种方法是等价的val a = 1 + 2 val a = 1.+(2)
- 反过来,可以定义这样的方法,并调用
-
例子1
object Cal { def say(x:Int):Int = x } object Main { def main(args: Array[String]): Unit = { // 1. 用 类.方法名(参数列表) 形式调用方法 var result1 = Cal.say(3) // 2. 用 类 方法 参数列表 形式调用方法 var result2 = Cal say (3) var result3 = Cal say 3 // 如果参数列表只有一个值,括号是可以省略的 } }
-
例子2
object Main { def say(x:Int):Int = x def main(args: Array[String]): Unit = { // 1. 用 类.方法名(参数列表) 形式调用方法 var result1 = this.say(3) // 2. 用 类 方法 参数列表 形式调用方法 var result2 = this say (3) // 如果参数列表只有一个值,括号是可以省略的 var result3 = this say 3 } }
-
1.2 函数
Scala中有23个函数接口类,函数的本质就是实现这些接口类来实现强大的功能:
Function0
:无参数,无返回值Function1
:1个参数,1个返回值- …
Function22
:22个参数,1个返回值
定义函数语法如下(函数能够在任意地方定义,而方法只能在类或对象中定义,作为属性而存在):
// 1. 非匿名函数,其中 ([变量类型,变量类型])=>返回值类型 是函数的类型声明
def 函数名:([变量类型,变量类型])=>返回值类型 = ([变量:变量类型,变量:变量类型]) => {
方法体
}
func(函数名) // 可作为实参,不过用的少
// 2. 匿名函数:通常作为实参存在
func(([变量:变量类型,变量:变量类型]) => {
方法体
}
)
// 3. 形参处定义函数
def add(func:([变量类型,变量类型]) => 返回值类型) = {
...
}
-
函数可以作为一个值传递
object Main { def main(args: Array[String]): Unit = { val add = (x:Int, y:Int) => x + y // 匿名函数 val result = add // 不会报错 println(result) } }
-
函数可以作为参数传递,此时需要指名函数的类型。格式为:
(参数类型)=>返回值类型
object Main { def main(args: Array[String]): Unit = { // 1. 定义calculate方法 def calculate(x:Int, y:Int, func:(Int, Int)=>Int): Int = { // x与y进行func操作,这个func函数需要指名 形参类型和返回值 func(x, y) } // 2. 定义add函数 def add = (x:Int, y:Int) => x + y // 3. 函数作为参数传递 val result = calculate(2, 3, add) // 4. 输出 println(result) // 输出5 } }
-
函数可以作为返回值传递
object Main { def main(args: Array[String]): Unit = { // 1. 定义getFunc方法 def getFunc() = { // x与y进行func操作,这个func函数需要指名 形参类型和返回值 val func = (x:Int, y:Int) => x + y func } // 2. 得到函数 println(getFunc()) // 3. 得到函数并执行函数 println(getFunc()(2, 3)) // 输出5 } }
-
函数可以执行
object Main { def main(args: Array[String]): Unit = { val add = (x:Int, y:Int) => x + y println(add(2, 3)) } }
1.3 方法转为函数
-
通用转换方式,在
方法名后面
+空格
+_
即可object Test { def main(args: Array[String]): Unit = { def add(x: Int, y: Int): Int = { x + y } def myPrint(x:Int, y:Int, func:(Int, Int)=>Int):Unit = { println(func(x,y)) } myPrint(10, 20, add _) // 方法转函数 } }
-
在参数处将方法转为函数时,可以简化,去掉
_
,直接方法名
object Test { def main(args: Array[String]): Unit = { def add(x: Int, y: Int): Int = { x + y } def myPrint(x:Int, y:Int, func:(Int, Int)=>Int):Unit = { println(func(x,y)) } myPrint(10, 20, add) // 在参数处将方法转为函数时,可以简化,去掉 _ } }
2. 编程至简原则
(10)当一个方法只有一个函数参数时,此时()
可用{}
代替。
// 待简化代码
spark.read.text(adInputs: _*).
filter((line_log_txt) => {
val line_data_list = line_log_txt.getString(0).split("\t")
line_data_list.length == 6}
})
// 1. 方法只有一个函数参数,可用 () 代替 {}
spark.read.text(adInputs: _*).
filter{(line_log_txt) => {
val line_data_list = line_log_txt.getString(0).split("\t")
line_data_list.length == 6}
}}
// 2. 只有一个参数,小括号可省略
spark.read.text(adInputs: _*).
filter{ line_log_txt => {
val line_data_list = line_log_txt.getString(0).split("\t")
line_data_list.length == 6}
}}
// 3. 外面有一层 {} 来表示多行代码了,可以省略内层的 {}
spark.read.text(adInputs: _*).
filter{ line_log_txt =>
val line_data_list = line_log_txt.getString(0).split("\t")
line_data_list.length == 6}
}
注意:方法最后一行的return可以省略,但是除此之外都不能省略。比如
object Test04_FuncSimply {
def main(args: Array[String]): Unit = {
//(1)return可以省略,Scala会使用方法体的最后一行代码作为返回值
def func1(x: Int, y: Int): Int = {
x + y
}
// (2)如果方法体只有一行代码,可以省略花括号
def func2(x: Int, y: Int): Int = x + y
//(3)返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
// 此时,函数就变为了数学表达式的形式:f(x, y) = x + y
def func3(x: Int, y: Int) = x + y
//(4)如果有return,则不能省略返回值类型,必须指定
def func4(x: Int, y: Int): Int = {
if (x < 20) {
return x + y
}
2 * x + 3 * y
}
//(5)如果方法明确声明unit,那么即使函数体中使用return关键字也不起作用
def func5(x: Int, y: Int): Unit = return x + y
//(6)Scala如果期望是无返回值类型,可以省略等号
def func6(x: Int, y: Int) {
println(x + y)
}
// (7)如果方法无参,但是声明了参数列表,那么调用时,小括号,可加可不加
def func7(): Unit = {
println("hello")
}
// (8)如果方法没有参数列表,那么小括号可以省略,调用时小括号必须省略
def func8 {
println("hello")
}
// (9)如果不关心函数名,只关心映射逻辑,就会变为lambda表达式
(x:Int, y:Int) => {println(x + y)}
val func = (x:Int, y:Int) => {println(x + y)} // 如果要设置函数名可以这样。此时,func就是函数名
}
}
3. 函数式编程
3.1 函数式编程思想
- 函数式编程思想:① 当做数学题,y = f(x),重要的是映射关系。② 使用val,在分布式上计算后不会产生歧义
- 通过前面可以知道,定义函数有3种形式:
- 方法转函数。先定义方法,再
方法名 _
- 直接定义函数
def
- 匿名函数
- 方法转函数。先定义方法,再
一看到
=>
或 一看到_
,就要想到这是表示函数。
3.3 函数柯里化&闭包
-
闭包:内层函数用到了外层函数变量,如果直接调用内层函数会取不到外层函数的这个变量值。此时,内层函数(万物皆对象,函数也是对象)的堆中的对象会保留一份引用到外层函数的值。
闭包参考链接 -
函数柯里化:将一个接收多个参数的函数转化成一个一个接受参数的函数过程,可以简单的理解为一种特殊的参数列表声明方式。函数柯里化
object TestFunction { val sum = (x: Int, y: Int, z: Int) => x + y + z // 函数柯里化的底层逻辑:本质是将函数作为返回值 val sum1 = (x: Int) => { y: Int => { // 匿名函数 z: Int => { // 匿名函数 x + y + z } } } // 函数柯里化的另一种简单表达 val sum2 = (x: Int) => (y: Int) => (z: Int) => x + y + z // 方法也有函数柯里化 def sum3(x: Int)(y: Int)(z: Int) = x + y + z def main(args: Array[String]): Unit = { sum(1, 2, 3) sum1(1)(2)(3) // sum1(1)调用完后,返回一个函数; sum1(1)(2)是调用返回的函数; ....... sum2(1)(2)(3) sum3(1)(2)(3) } }
3.5 递归 & 尾递归
-
递归与Java中的递归一样:前面知道scala的方法返回值是可以省略的,默认分配返回值类型,但是 如果方法是递归方法,则必须指定方法的返回值类型,否则会报错。
object Test{ def main(args: Array[String]): Unit = { // 实现阶乘 def fact(n : Int) : Int = { // 必须指名方法的返回值类型 // 跳出递归 if(n == 1) return 1 // 递归逻辑 n * fact(n - 1) } // 调用阶乘方法 println(fact(5)) } }
-
尾递归:递归是将每次调用函数/方法会压入到栈中,是累计使用资源,容易造成栈溢出;而尾递归是覆盖使用资源,不会造成栈溢出。所以,尾递归资源利用率更加高。尾递归参考链接
一般支持函数式编程语言都支持尾递归;但是Java不支持尾递归。
4. 补充
4.1 访问元祖元素
变量名._数字
比如:x._1 表示访问x的第一个元素
4.2 =>的含义
https://blog.csdn.net/qq_43546676/article/details/130992479
4.3 下划线的使用总结
更多推荐
所有评论(0)