Scala函数式编程
Scala是一门完全面向对象编程,以及完全函数式编程
**对象的本质:**对数据和行为的一个封装
**函数的本质:**函数考研当作一个值进行传递
Java和Scala不同,Java并不是完全面向对象编程语言,它含有基本数据类型,基本类型不是通过new对象的形式进行表示,这些基本数据类型要通过包装类(包装类提供了将基元转换为对象和对象转换为基元的机制)
1、函数基础
1.1 函数基本语法
def sum(x:int,y:Int):Int={
x+y}
/* def 定义函数的关键字
* sum 函数名
* x,y:参数名
* Int:参数类型
* :Int= 函数返回值类型
* x+y 函数体
*/
函数的使用
object Function {
def main(args:Array[String]):Unit={
// 函数定义
def sum(x:Int,y:Int):Int={
x+y
}
//函数调用。传入两个参数10,20
print(sum(10,20))
}
}
// 输出 30
1.2 函数和方法的区别
函数:为了完成某一部分功能的程序语句的集合;
方法:在类中定义的函数
注意点:
-
Scala语言可以在任何的语法结构中声明任何的语法
-
函数没有重载和重写的概念;方法可以进行重载和重写
-
Scala中函数可以嵌套定义
object TestFunction { // (2)方法可以进行重载和重写,程序可以执行 def main(): Unit = { } def main(args: Array[String]): Unit = { // (1)Scala 语言可以在任何的语法结构中声明任何的语法 import java.util.Date new Date() // (2)函数没有重载和重写的概念,程序报错 def test(): Unit ={ println("无参,无返回值") } test() def test(name:String):Unit={ println() } //(3)Scala 中函数可以嵌套定义 def test2(): Unit ={ def test3(name:String):Unit={ println("函数可以嵌套定义") } } } }
1.3 函数定义
函数定义的方式
-
函数1:无参,无返回值
-
函数2:无参,有返回值
-
函数3:有参,无返回值
-
函数4:有参,有返回值
-
函数5:多参,无返回值
-
函数6:多参,有返回值
object Function { def main(args:Array[String]):Unit={ // 函数的定义 def add(x:Int,y:Int):Int={ x+y } val function = new Function() // 使用方法,类中的函数 print(function.add1()) print(function.add("张三")) } } class Function{ // 函数的定义方式 // 无参,无返回值 def add1(): Unit = print("这是无参,无返回值的函数") // 无参,有返回值 def add():String= "这是无参,有返回值的函数" // 有参,无返回值 def add1(x:Int)=print("这是有参,无返回值的函数"+"\t"+x) // 有参,有返回值 def add(x:String):String= "这是有参,有返回值的函数"+"\t"+x // 多参数,无返回值 def add1(x:String,y:String)=print("这是多参数,无返回值的函数") // 多参数,有返回值 def add(x:String,y:String)="这是多参数,有返回值的函数"+"\t"+x+"\t"+y }
1.4 函数参数
函数可接的参数:
(1)可变参数
(2)如果参数列表中存在多个参数,那么可变参数一般放置在最后
(3)参数默认值,一般将有默认值的参数放置在参数列表的后面
(4)带名参数
object TestFunction {
def main(args: Array[String]): Unit = {
// (1)可变参数
def test( s : String* ): Unit = {
println(s)
}
// 有输入参数:输出 Array
test("Hello", "Scala")
// 无输入参数:输出 List()
test()
// (2)如果参数列表中存在多个参数,那么可变参数一般放置在最后
def test2( name : String, s: String* ): Unit = {
println(name + "," + s)
}
test2("jinlian", "dalang")
// (3)参数默认值
def test3( name : String, age : Int = 30 ): Unit = {
println(s"$name, $age")
}
// 如果参数传递了值,那么会覆盖默认值
test3("jinlian", 20)
// 如果参数有默认值,在调用的时候,可以省略这个参数
test3("dalang")
// 一般情况下,将有默认值的参数放置在参数列表的后面
def test4( sex : String = "男", name : String ): Unit = {
println(s"$name, $sex")
}
// Scala 函数中参数传递是,从左到右
//test4("wusong")
//(4)带名参数
test4(name="ximenqing")
}
}
1.5 函数的至简原则
函数的至简原则:能省就省
至简规则如下:
(1)return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
(2)如果函数体只有一行代码,可以省略花括号
(3)返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
(4)如果有 return,则不能省略返回值类型,必须指定
(5)如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
(6)Scala 如果期望是无返回值类型,可以省略等号
(7)如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
(8)如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
(9)如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略,变成匿名函数lambda
object Function_1 {
def main(args: Array[String]): Unit = {
// scala的至简原则
// 标准形式
def f(): Unit ={
println("这是scala最标准的方式,无参,无返回值")
}
f()
// 1、return 可以省略,Scala编译器默认把最后一行当作输出
def f1(x:String):String={
"scala至简原则第一条"+"\t"+x
}
println(f1("至简"))
// 2、如果只有一条输出语句,可以省略大括号,数据类型可以为任意类型
def f2():String="这是scala至简原则第二条"
println(f2())
}
// 3、如果返回可以推断出来,那么可以省略 :数据类型
def f3()={
"这是scala至简原则第三条"
}
println(f3())
// 4、如果写了return,一定要说明数据类型
def f4():String={
return "这是Scala至简原则第四条"
}// 不加会报错:method f4 has return statement; needs result type
// 5、至简原则第五条,如果返回类型是Unit,那么可以省略,跟第三点类似
def f5(){
println("这是Scala至简原则第五条")
}
// 6、至简原则第五条,如果参数是无参,在定义函数的时候加了括号,那么在调用函数的时候,括号可加可不加
f5()
f5 // 两个方法都可以调用
//7、至简原则第六条,在定义函数的时候没有加括号,那么在调用函数的时候,不可以加括号
def f6 ={
println("至简原则第七条")
}
f6
//8、如果只是追求函数的内部逻辑,并不在意函数名,可以省略def 以及函数名,但是要添加>
def test(f:(Int,Int)=>Int):Unit ={
val result=f(10,20)
println(result)
}
def sum(x:Int,y:Int): Int ={
x+y
}
// 一种方法 test(sum)
// 另外一种方法
test((x:Int,y:Int)=>{x+y})
}
1.6 函数即对象
Scala是一门完全面向对象的编程语言,即Scala面向对象
Scala同样是一门函数式编程的编程语言,即Scala面向函数
1.6.1 函数对象
将函数对象作为变量使用
/*
* 在java中,创建一个对象是使用User user=new User()
* 在Scala中 使用 val f=test _ 即可以表示
* 此时f可以通过编译器自动推断类型,函数对象的类型称之为函数类型Function0[Unit]
* 这里类型中的0:表示函数参数列表中参数的个数 Unit:表示函数没有返回值
*/
val f=test _
//如果明确变量类型,那么不使用下划线也可以将函数作为整体传递给变量
var f()=>Unit=test
val f:Function0[Unit]=test _
// 两个的效果是一样的,若有参数,需要将参数进行指定,以及返回值进行指定
def sum(x:Int,y:Int): Int ={
x+y
}
// 将函数对象赋值给一个变量,那么这个变量其实就是函数,变量可以传参后执行 f()
val f:Function2[Int,Int,Int]=sum _
println(f)
// Function_1$$$Lambda$6/2096171631@7e0babb1
注意:
-
函数对象的参数最多只能22个
-
函数对象和函数的不同:函数的参数个数没有个数,但是把函数当作对象进行使用的时候,函数对象的参数只能22个
1.6.2 函数对象的参数
将函数对象作为参数使用
/*
* java的引用
* public void test(User user){
* user.xxx()'' }
* User user=new User();
* test(user)
*/
def fun():Unit=println("test...")
// test函数的参数是一个函数对象
def test(f:()=>Unit):Unit={
f()
}
// 将函数对象赋值为一个变量
val f=fun _
// 这个变量(函数对象)传入到另外一个函数中
test(f)
1.6.3 函数对象的返回值
可以将函数对象作为返回值进行输出
// 函数的返回类型一般下都不声明,使用自动推断
def outer():Uint={
def inner():Unit={
print("inner.....")
}
inner _
}
// 使用,此时,f就是函数对象,有函数类型,()=>Unit
val out=outer()
// 调用函数
f()
// 函数编程的写法·1
outer()()
1.7 lambda至简原则
1、下划线的省略
1)将函数名称直接作为参数传递给另外一个函数,不需要使用下划线
2)使用下划线的目的是不让函数执行,而是将它作为对象使用,那么如果能明确函数不执行,那么可以不加下划线
def fun():Unit=print("test...")
// 这两种方法都可以执行
val f=fun _
val f:()=>Unit =fun
object Function_lambda {
// 调用函数对象,这个f需要传两个int值 x,y
// 并返回一个int值 z
// int值 z 将作为test的参数进行执行,返回值为Unit
def main(string: Array[String]): Unit = {
def test(f: (Int, Int) => Int): Unit = {
val result = f(10, 20)
println(result)
}
def sum(x: Int, y: Int): Int = {
x + y
}
//test(sum) 不使用这种方法
// 如果函数没有名称和def,那么是匿名函数,一般就是作为参数使用
// 这样可以减少sum这个函数的使用,不在意函数名,更在意函数内部结构
test(
(x: Int, y: Int) => {
x + y
}
)
// 匿名函数作为参数使用时,可以采用【至简原则】
// 是指函数在使用过程中进行一个至简
//1、匿名函数的逻辑只有一行代码,那么大括号可以省略
test(
(x: Int, y: Int) => x + y
)
//2、匿名函数的参数类型如果可以推断出来,那么参数类型可以省略
test(
(x, y) => x + y
)
// 3、匿名函数中如果参数列表的个数只有一个,那么小括号可以省略
def fun(f: (String) => Unit): Unit = {
f("zhangsan")
}
def test1(name: String): Unit = {
println(name)
}
// 匿名函数第一步
fun(
(name) => {
println(name)
}
)
// 匿名函数第二步
// 如果只有一行代码可以1)省略大括号,2)参数可以省略,3)小括号可以省略,4)参数只是用一次,可以使用下划线代替
fun(println(_))
// 4、匿名函数中如果参数按照顺序只执行一次的场合,那么可以使用下划线代替参数,省略参数列表
test(
_ + _
)
}
}
1.8 闭包与柯里化
1.8.1 闭包
如果一个函数使用了外部的变量,但是改变这个变量的生命周期;将这个变量包含到当前函数的作用域,形成闭合的效果(环境),这个环境称之为闭包环境,简称闭包
def outer(x:Int):={
def inner(y:Int):Unit={
print("inner.....")
}
inner _
}
val inner=outer(10)
val result=inner(20)
println(result)
// 这个函数在代码上可以行得通,但是在逻辑上行不通,因为在逻辑上当一个函数执行完之后,就会收回那个函数中定义的变量,无法使用到另外一个函数里面
闭包的场景
1、内部函数是用来外部的数据,改变数据的声明周期
2、将函数作为对象使用,改变函数本身的生命周期
3、所有匿名函数都有闭包
4、内部函数返回到外部使用也会有闭包
1.8.2 柯里化
在函数传递参数的时候,参数之间并没有关系,那么如果在传值的时候,同时传递,会存在耦合性,而且增加了函数的执行复杂性,必须所有参数都执行完成,函数才可以执行完成。
柯里化:就是为了将函数简单化,将无关的参数进行分离,可以设定多个参数列表
// 函数柯里化,其实就是将复杂的参数逻辑变得简单化,函数柯里化一定存在闭包
def f3(a:Int)(b:Int)={
a + b
}
println(f3(10)(3))
}
1.9 递归函数
一个函数/方法在函数/方法体内又调用了本身,我们称之为递归调用
// 函数递归
def digui(x:Int):Int={
// 阶乘
// 递归算法
// 1) 方法调用自身
// 2) 方法必须要有跳出的逻辑
// 3) 方法调用自身时,传递的参数应该有规律
// 4) scala 中的递归必须声明函数返回值类型
if (x==1)
x
else
x*digui(x-1)
}
val result=digui(5)
println("递归的结果是"+result)
注意
Java的栈内存是有大小限制的。方法执行时,压栈的内存也是有大小的,那么栈内存不可能无限压栈;如果压栈的此时超过阈值,就会出现错误,即使有跳出的逻辑也会出错。
Scala采用了一种特殊的语法优化递归操作**:尾(伪)递归**。Scal采用while循环实现尾递归;java中尾递归没有优化
// 这样将递归函数放在尾部去执行,就可以实现尾递归的效果
def test():Unit={
println("test...")
test()
}
test()
1.10 惰性函数
当函数返回值被声明为 lazy 时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数。
def main(args: Array[String]): Unit = {
lazy val res = sum(10, 30)
println("----------------")
println("res=" + res)
}
def sum(n1: Int, n2: Int): Int = {
println("sum 被执行。。。")
return n1 + n2
}
输出结果:
----------------
sum 被执行。。。
res=40
注意:lazy 不能修饰 var 类型的变量