一、概述
学习函数时编程之前我们先来了解一下目前比较流行的、不同的编程范式。
编程范式:
面向过程:将问题拆解为一步一步,按照步骤解决问题。
面向对象:分解对象、行为、属性,通过对象关系以及行为调用解决问题。耦合低,可维护性强。
函数式编程:解决问题时,将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的步骤,解决问题。
函数式编程语言中,所有值都是常量,都是一个值。Scala
中推荐大家能用常量就用常量(val
),符合函数式编程的基本思想。函数式编程中每段程序都会有一个返回值,(if - else
、for
),本质上就是一个映射关系,表达式进行求值,做函数的映射关系。
函数式编程不关心计算机底层的实现,对开发者更加友好。命令式编程对于计算机更加的友好,执行效率比较高,函数式编程对于开发者的效率更高,但是执行效率比较低。函数式编程无副作用,利于并行处理,所以 Scala 特别利于应用于大数据处理,比如Spark
,Kafka
。
二、函数基础
基本语法
如何定义一个函数?
def 函数名称 ( 参数名 : 参数类型 , ......) : 函数返回值类型 = {
函数体;
}
复制代码
特点说明:
在 Scala 中,函数在代码块的任何地方都可以单独去声明出来。
定义在方法中(内层)的称为函数(狭义的函数),定义在类或对象中(最外层)的函数称为方法
默认使用最后一行代码作为返回值,return
可省略
函数没有重载和重写的概念;方法可以进行重载和重写
举个栗子:
object Test01_Function {
def main(args: Array[String]): Unit = {
// 定义一个函数
def sayHi(name: String): Unit = {
println(name + ", sayHi!")
}
// 调用函数
sayHi("lisi")
// 调用方法 通过对象去调用
Test01_Function.sayHi("wangwu")
// 获取方法返回值
val result = Test01_Function.sayHello("zhaosan")
println(result)
}
// 定义对象的方法
def sayHi(name: String): Unit = {
println("Hi , "+name)
}
def sayHello(name: String): String = {
println("hello , "+name)
return "hello"
}
}
复制代码
函数参数
Scala 中定义函数参数可以有默认值,指的是如果当前的函数声明时指定了默认值,调用的时候可以不传参数,此时该参数的值为默认值,默认参数必须全部放在末尾。
可变参数。参数列表中如果有多个参数,可变参数放在最后。
带名参数:指定参数传值的时候可以带着名称去传值,在调用函数时与参数的位置无关,根据名称可以确定对应的参数。
举个栗子:
object Test02_FunctionParameter {
def main(args: Array[String]): Unit = {
// 可变参数
// WrappedArray 底层会转变成一个集合类型,类似于Java中的数组
def f1(str: String*): Unit = {
println(str)
}
f1("alice")
f1("alice", "aaa", "bbbb")
def f2(str1: String, str2: String*): Unit = {
println(str1 + " , " + str2)
}
f2("alice")
f2("alice", "aaa", "bbbb")
//参数默认值
def f3(name: String = "lisi"): Unit = {
println(name)
}
f3()
f3("wangwu")
// 带名参数
def f4(name: String = "lala", age: Int): Unit = {
println(s"${age}岁的${name}在吃饭")
}
f4("bob", 12)
f4(age = 13, name = "haha")
f4(age = 15)
}
}
复制代码
函数的至简原则
def f1(name: String): String = {
name
}
复制代码
def f2(name: String): String = name
复制代码
def f3(name: String) = name
复制代码
def f4(name: String): String = {
return name
}
复制代码
def f5(name: String): Unit = {
return name
}
复制代码
def f6() {
"hello world"
}
复制代码
def f7() = "test"
println(f7())
println(f7)
复制代码
def f8 = "hello"
println(f8)
复制代码
(name: String)=>{ println(name) }
复制代码
三、函数高阶应用
匿名函数
定义:所谓匿名函数,就是没有名字的函数,也叫做lambda
表达式。匿名函数定义时不能有函数的返回值类型。
(x:Int)=>{ 函数体 }
// x:表示输入参数类型;Int:表示输入参数类型;函数体:表示具体代码逻
复制代码
匿名函数的简化原则
举两个栗子:
// 将整个函数作为一个值赋给变量中
val fun= (name: String) => { println(name) }
fun("wangwu")
// 定义一个函数,以函数作为参数输入
def f(func: String=> Unit): Unit={
func("lisi")
}
f(fun)
f((name: String) => { println(name) })
f(name => println(name))
f(println(_))
f(println)
复制代码
// 定义一个二元运算函数,只操作1和2,具体的运算过程通过参数传入
def dualFunctionOneAndTwo(func: (Int,Int) => Int ): Int = {
func(1,2)
}
// 简化
println(dualFunctionOneAndTwo((a: Int,b: Int) => a+b))
println(dualFunctionOneAndTwo((a: Int,b: Int) => a-b))
println(dualFunctionOneAndTwo((a,b) => a+b))
println(dualFunctionOneAndTwo((a,b) => a-b))
println(dualFunctionOneAndTwo(_+_))
复制代码
高阶函数
Scala 中的高阶函数有三种方式:函数作为值进行传递、函数作为参数传递、函数作为函数的返回值。
函数作为值进行传递
经过赋值之后在底层变成一个 lambda 对象。
// 定义一个函数
def f(n: Int): Int = {
println("f调用")
n + 1
}
// 前面为参数类型 后面为返回值类型
val f1: Int => Int = f
// 等价于
val f2 = f _ // f _ 表示f函数本身
println(f1) // demo04.Test06_HighOrderFunction$$$Lambda$5/1337344609@782663d3
println(f2) // demo04.Test06_HighOrderFunction$$$Lambda$6/1113619023@1990a65e
println(f1 == f2) //false
复制代码
f1 和 f2 实际上是函数的引用 scala 底层是一个完全面向对象、函数式编程语言
函数作为参数传递
可以传匿名函数、函数名称、lambda 对象。
// 定义二元运算函数
def dualEval(op: (Int, Int) => Int, a: Int, b: Int): Int = {
op(a, b)
}
def add(a: Int, b: Int): Int = {
a + b
}
println(dualEval(add, 12, 32))
println(dualEval((a, b) => a + b, 12, 32))
println(dualEval(_ + _, 12, 32))
复制代码
函数作为函数的返回值
def outerFunc(): Int => Unit = {
def inner(a: Int): Unit = {
println(s"call inner with argument ${a}")
}
inner // return a function
}
println(outerFunc()(10))
复制代码
举三个栗子:
使用特定操作处理数组元素,得到新数组。也就是集合处理的 map(映射)操作。
object Test07_Practice_CollectionOperation {
def main(args: Array[String]): Unit = {
val arr: Array[Int] = Array(12, 23, 22, 11, 65, 66, 44)
// 对数组进行处理 将操作抽象出来 ,处理完结果返回一个新的数组
def arrayOperation(array: Array[Int], op: Int => Int): Array[Int] = {
for (elem <- array) yield op(elem)
}
// 定义一个加1操作
def addOne(elem: Int): Int = {
elem + 1
}
// 调用函数
val newArray: Array[Int] = arrayOperation(arr, addOne)
println(newArray.mkString(","))
// 传入匿名函数,实现元素翻倍
val newArray2 = arrayOperation(arr, _ * 2)
println(newArray2.mkString(","))
}
}
复制代码
定义一个匿名函数,并将它作为值赋给变量 fun。函数有三个参数,类型分别为 Int,String,Char,返回值类型为 Boolean。要求调用函数 fun(0, “”, ‘0’)得到返回值为 false,其它情况均返回 true。
val fun = (i: Int, S: String, c: Char) => {
if (i == 0 && S == " " && c == '0') false else true
}
println(fun(0, " ", '0'))
println(fun(0, " ", '1'))
println(fun(2, " ", '1'))
println(fun(0, "lisi", '0'))
复制代码
定义一个函数 func,它接收一个 Int 类型的参数,返回一个函数(记作 f1)。它返回的函数 f1,接收一个 String 类型的参数,同样返回一个函数(记作 f2)。函数 f2 接收一个 Char 类型的参数,返回一个 Boolean 的值。要求调用函数 func(0) (“”) (‘0’)得到返回值为 false,其它情况均返回 true。无限套娃
def func(i: Int): String => (Char => Boolean) = {
def f1(s: String): Char => Boolean = {
def f2(c: Char): Boolean = {
if (i == 0 && s == " " && c == '0') false else true
}
f2
}
f1
}
// 匿名函数的简写
def func1(i: Int): String => (Char => Boolean) = {
(s: String) => {
(c: Char)=> {
if (i == 0 && s == " " && c == '0') false else true
}
}
}
// 持续简化
def func2(i: Int): String => (Char => Boolean) = {
s => c => if (i == 0 && s == " " && c == '0') false else true
}
// 柯里化
def fun3(i: Int)(s: String)(c: Char): Boolean={
if (i == 0 && s == " " && c == '0') false else true
}
def func4(i: Int)(s: String)(c: Char): Boolean = !(i == 0 && s == "" && c == '0')
复制代码
柯里化 &闭包
闭包定义:如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包。
def addByA(a: Int): Int => Int = {
def addB(b: Int): Int = {
a + b
}
addB
}
// 简化
def addByA1(a: Int): Int => Int = {
(b: Int) => {
a + b
}
}
// 简化
def addByA2(a: Int): Int => Int = {
b => {
a + b
}
}
// 简化
def addByA3(a: Int): Int => Int = {
b => a + b
}
// 简化
def addByA4(a: Int): Int => Int = {
a + _
}
// 简化到最后
def addByA5(a: Int): Int => Int = a + _
复制代码
柯里化定义:将一个参数列表的多个参数,变成多个参数列表的过程。也就是将普通多参数函数变成高阶函数的过程。
def addCurrying(a: Int)(b: Int): Int = a + b
println(addCurrying(21)(23))
复制代码
递归
Scala 中的尾递归优化:
// 递归实现计算阶乘
def fact(n: Int): Int = {
if (n == 0) return 1
fact(n - 1) * n
}
// 尾递归
def tailFact(n: Int): Int = {
@transient // 保证写的代码是一个尾递归
def loop(n: Int, res: Int): Int = {
if(n == 0) return res
loop(n-1,res * n)
}
loop(n,1)
}
复制代码
控制对象
举两个栗子:
// 1. 传值参数
// 把参数a替换成 12
def f0(a: Int): Unit = {
println("a: " + a)
println("a: " + a)
}
println(f0(21))
def f1(): Int = {
println("f1调用")
12
}
println(f0(f1()))
// 2。传名参数
// =>Int 表示一段代码块 代码块的返回值为Int
// 把参数a全部替换为 f1()
def f2(a: => Int): Unit = {
println("a: " + a)
println("a: " + a)
}
f2(21)
f2(f1())
f2({
println("这是一个代码块")
12
})
复制代码
使用传名参数实现一个函数相当于 while 的功能
object Test12_MyWhile {
def main(args: Array[String]): Unit = {
var n = 10
// 1.常规while循环
while (n >= 1) {
println(n)
n -= 1
}
// 2.自定义函数实现while功能
// 用闭包实现函数,将代码块传入
def myWhile(condition: => Boolean): (=> Unit) => Unit = {
// 内层函数需要递归调用 参数为循环体
def doLoop(op: => Unit): Unit = {
if (condition) {
op
myWhile(condition)(op)
}
}
doLoop _
}
n = 10
myWhile(n >= 1) {
println(n)
n -= 1
}
// 3.简化
def myWhile2(condition: => Boolean): (=> Unit) => Unit = {
// 内层函数需要递归调用 参数为循环体
op=>{
if (condition) {
op
myWhile2(condition)(op)
}
}
}
// 4.柯里化
def myWhile3(condition: => Boolean)(op: Unit): Unit={
if (condition) {
op
myWhile3(condition)(op)
}
}
n = 10
myWhile3(n >= 1) {
println(n)
n -= 1
}
}
}
复制代码
惰性函数
定义:当函数返回值被声明为 lazy
时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数。lazy 不能修饰 var 类型的变量
lazy val result: Int=sum(12,34)
println("1. 函数调用")
println("2. result = ",result)
def sum(i: Int, i1: Int): Int ={
println("3 ,sum调用")
i + i1
}
复制代码
与传名参数比较类似,但懒加载只是推迟求值到第一次使用时,而不是单纯替换。
本次分享的内容到这里就结束了,希望对大家学习 Scala 语言有所帮助!!!
评论