scala基础之函数和闭包

来源:互联网 发布:高清油画 知乎 编辑:程序博客网 时间:2024/06/05 00:42

一 函数的类型

1.1 不带返回值的类型

defsayHello() {
    println("hello")
}
# Unit 和 java的返回值类型void是一样的
def sayHi():Unit = {
    println("hi")
}

1.2 带有返回值的函数

def sum(a:Int, b:Int):Int = {    a + b}

1.3 递归函数

def fab(n: Int): Int = {    if(n <= 1) 1    else fab(n - 1) + fab(n - 2)}

 

1.4 偏函数

偏函数,一种函数的高级形式,如果你想定义一个函数,而让它只接受和处理其参数定义域范围内的子集,对于这个参数范围外的参数则抛出异常,这样的函数就是偏函数(顾名思异就是这个函数只处理传入来的部分参数)。

偏函数是PartialFunction[A,B]类的一个实例,A是可以接收的类型,B是需要返回的类型。

这个类有2个方法:

isDefinedAt函数:主要用于判断输入的参数是否在这个偏函数所处理的范围之中,如果没有则报错:scala.MatchError

apply函数:调用偏函数的时候,实际上内部内部调用的就是apply函数,它会根据内提供的值进行匹配,然后将结果返回

Scala中的Partial Function就是一个“残缺”的函数,就像一个严重偏科的学生,只对某些科目感兴趣,而对没有兴趣的内容弃若蔽履。Partial Function做不到以“偏”概全,因而需要将多个偏函数组合,最终才能达到全面覆盖的目的。所以这个Partial Function确实是一“部分”的函数。

def partial:PartialFunction[Int,String] = {    case i if i < 10 => "[-10]"    case i if i >= 10 && i < 20 => "[10-20]"    case i if i >20 => "[20+]"}def p:PartialFunction[Int,Int] = {    case 1 => 1    case 2 => 2 * 2    case 3 => 3 * 3 * 3    case 4 => 4 * 4 * 4 * 4}
print(p.isDefinedAt(4)) //trueprint(p.isDefinedAt(5)) //false

 

偏函数结合collect函数使用:

val numList = List(1,2,3,4)val collectList = numList.collect(p)for (element <- collectList) {    println(element+" ")}

 

1.5 匿名函数

在scala中,函数可以没有名字,一般使用使用这个函数都是逻辑很简单的一些函数,或者计算简单。

(x:Int) => {x * 3}

我们也可以将匿名函数存放在一个变量中

val result = (x:Int) => {x * 3}

等价于标准的函数:

def result(x:Int):Int = {    x * 3}
 
有的时候我们可以将匿名函数作为一个映射器或者过滤器,结合map盒或者filter使用,如下:
val result = Array(1,2,3,4,5).map{(x:Int) => x * 3}for (ret <- result) {    print(ret+" ")}

 

1.6 高阶函数(带函数参数的函数)

高阶函数:就是可以接收其他函数的函数或者返回一个函数的函数

我们常见的高阶函数就包括map,filter等

高阶函数分为2种情况:

1.6.1 将函数作为参数

语法形式:

第一种:高阶函数不带参数

def 高阶函数名(传入的函数名:(传入的函数的参数类型) => 返回类型) {调用传入的函数(传入的参数)}

def sum(f:(Int,Int) => Int):Int = {    val x = 10    val y = 20    f(x,y)}def sum(f:(Int,Int) => Int):Int = f(10,20)

这两个函数是等价的,下面的只有一行,所以不用加{}

 

第二种:高阶函数带参数

def 高阶函数名(传入的函数名:(传入的函数的参数类型) => 返回类型, p1:数据类型,p2:数据类型...) {调用传入的函数(传入的参数)}

 

/*第一步:定义一个需要传入高阶函数的函数*/def avg(numList:List[Int],factor:Int) : Double = {    if (numList == Nil || numList.isEmpty)        return 0.0;    val sum = numList.sum    sum / factor}/*第二步:创建高阶函数*/def execute(call:(List[Int],Int) => Double, numList:List[Int],factor:Int, other:Double):Double = {    call(numList,factor) + other}/*第三步:调用execute高阶函数*/def run(): Unit ={    val numList = List(1,3,5,7)    val factor = 4    val other = 10.0    execute(avg,numList,factor,other)}

 

 

1.6.2 将函数作为返回值

语法形式

def 高阶函数名(传入的参数) = 匿名函数/已经定义的函数

/* * 使用高阶函数返回一个函数,很多的时候是配合匿名函数来做的 * 当然你也可以定一个一个普通的函数来返回 *///返回一个匿名函数def funcAsReturnValue(desc:String) = (p1:Long,p2:Long) => println(desc + (p1 + p2))//返回一个已经定义的函数def funcAsReturnValue(numList:List[Int],factor:Int) = avg(numList,factor)

 

1.6.3 高阶函数的类型推断

当你将一个函数传递给一个函数时,scala会尽可能帮你推断出类型信息,即你在调用高阶函数的时候,你传递的参数,可以不用指定数据类型,而只是指定变量名称即可(x,y) => x+y;对于只有一个参数的函数,我们还可以省略小括号 x => x * 3

def convertIntToString(f:(Int) => String) = f(4)
def sum(f:(Int,Int) => Int):Int = {    val x = 10    val y = 20    f(x,y)}
//最开始的写法sum((x:Int,y:Int) => x + y)//简单的写法sum((x,y) => x + y)//对于只有一个参数的函数,你还可以省略小括号//最开始的写法convertIntToString((x:Int) => x+" ")//简单的写法convertIntToString(x => x +"")

 

 

1.7Currying函数(柯里化)

其实就是函数的链式调用。语法格式如下:

函数(自己本身的参数列表)(返回的函数的参数列表)

我们看一下正常的函数调用情况:

//定一个高阶函数,返回一个函数def calculate(a:Int,b:Int) = (x:Int,y:Int) => (a + b) / (x + y)
//调用高阶函数calculate返回一个函数val func = calculate(10,20)//调用返回的函数,得到结果val result = func(1,2)println("Non-Currying Function Result => "+result)//柯里化函数val ret = calculate(10,20)(1,2)println("Currying Function Result =>" + result)

 

 

//定义柯里化函数def info(protagonist:List[String])(author:String)(bookName:String) : Unit = {    println("主人公=>"+protagonist.mkString(" ")+" 作者=>"+author+" 小说=>"+bookName)}

 

//调用定义柯里化函数info(List("郭靖","黄蓉"))("金庸")("射雕英雄传")
结果: 主人公=>郭靖黄蓉作者=>金庸小说=>射雕英雄传

 

 

二 函数参数

2.1 默认参数

在java中默认参数只有我们在判断参数是否为空,然后给这个参数给与一个默认值:

public static voidconnect(Stringhost, intport){
    if (host == null) {
        host = "localhost";
    }
    if (port == 0) {
        port = 3306;
    }
    System.out.println("连接成功");
}

但是在scala中,我们在定义函数的时候通过(变量名:数据类型 = 默认值)的形式给与参数默认值

//默认参数
def connect(host:String= "localhost", port:Int=

           3306):Unit = {
    print("host=>"+host+" port=>"+port)
}

 

2.2 普通参数

//普通参数
def connect(host:String,port:String,username:String,password:String) {
    println("Host: "+host+">> port: "+port+">> username: "+username
       
+"password: "+password)
}

2.3 可变长参数

Java实现:

public static intsum(int... nums){
    int sum = 0;
    for (int num : nums) {
        sum += num;
    }
    return sum;
}

scala实现:

//可变长参数,和python传入不定长参数有点类似def sum(nums:Int*):Int = {    val numsLength = nums.length;    val numsHead  = nums.head;    val numsTail = nums.tail;    var sum = 0;    for (num <- nums){        sum = sum + num;    }    sum}

 

//如果我们想将一个序列传递给可变长参数函数,那么不能直接使用to
//
错误的写法: sum(1 to 5)编译就会报错
//
我们此时需要借助特殊的语法格式1 to 5_*
sum
(1 to 10:_*)

 

三 函数lazy的值

Scala提供lazy特性,即如果将一个变量声明为lazy,则只有在第一次使用该变量的时候,变量对应的表达式才会发生计算,对于特别耗时的计算操作比较有用,比如打开文件进行IO或者网络IO

/*立马读取文件*/
val lines= Source.fromFile("E:\\Bench\\BigData\\scala\\ScalaMain\\content.txt").mkString

/*懒读取文件内容:只有当使用到lazy变量才会触发读取文件的操作,否则不会真实读取,比如print(line)就是使用到lazy 变量了*/
lazy val line= Source.fromFile("E:\\Bench\\BigData\\scala\\ScalaMain\\content.txt").mkString

/*函数也不会立即执行,只有函数被滴调用的时候才会触发*/
def func= Source.fromFile("E:\\Bench\\BigData\\scala\\ScalaMain\\content.txt").mkString
print(line)

 

 

四 闭包

闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量通常,我们可以理解为能够访问函数局部变量的另外一个函数。

/* * val triple = mulBy(3) * 首先调用mulBy(3) 产生一个函数 (x:Double) => 3 * x * triple(10): 调用的是mulBy(3)产生的函数(x:Double) => 3 * x = 30.0 * * 首先调用mulBy(0.5) 产生一个函数 (x:Double) => 0.5 * x * half(0.5): 调用的是mulBy(3)产生的函数(x:Double) => 0.5 * x = 5.0 * * 注意:我们每一次传递给函数的factor是不一样的,每一次返回的函数都有自己的factor设置 * 这样的一个函数就是闭包 */val triple = mulBy(3)val half = mulBy(0.5)println(triple(10) +"==="+half(10))

 

五 常见的高级函数使用

5.1map 一对一映射,接收一个函数或者匿名函数或者一个表达式,将一个集合的某一个元素根据提供的函数或者表达式映射成另外一个新的集合

def main(args: Array[String]) {    val nums = Array[Int](1,2,3,4,5)    val resultList = nums.map(pow(_,3))    val results = nums.map(_ * 3)    resultList.foreach(element => print(element+" "))    results.foreach(element => print(element+" "))}def pow(x:Int,y:Int): Double = {    math.pow(x,y)}

 

5.2flatmap 先将目标映射然后再扁平化相当于两个操作map+flattern

def flatmap(elementList:List[List[Int]]) : Unit = {    // flattern操作:List(1, 3, 5, 7, 9, 2, 4, 6, 8, 10)    val tempList = elementList.flatten    //map 操作:List(0, 2, 4, 6, 8, 1, 3, 5, 7, 9)    print(tempList.map(_ - 1))    //flatmap操作    println(elementList.flatMap(x => x.map(_ - 1)))}

 

5.3Collect + 偏函数

val numList = List(1,2,3,4)val collectList = numList.collect(p)for (element <- collectList) {    println(element+" ")}

 

5.4reduceLeft & reduceRight

/** * reduceLeft的计算过程: * 从左边开始,左边数据右边数据进行 */def reduceLeft(): Unit = {    val numList = List(1,2,3,4,5)    val result1 = numList.reduceLeft(_ - _)    print(result1)    //分析    //1-2 = -1-3 = -4-4 = -8 -5 = 13    val result2 = numList.reduceLeft(_ * _)    //1*2 =2 * 3 = 6 * 4=24*5=120    print(result2)}/** * reduceRight的计算过程: * 从右边开始,左边数据右边数据进行 */def reduceRight(): Unit = {    val numList = List(1,2,3,4,5)    val result1 = numList.reduceRight(_ - _)    print(result1)    //分析    //4-5 = -1 => 3-(-1) = 4 => 2-4 = -2=> 1-(-2) =3}

 

5.5foldLeft & foldRight

foldLeft: 指定一个初始值,然后从左往右进行运算,指定的值和第一个元素运算,然后结果和第二个元素进行运算,一直到最后

scala> List(1,2,3,4,5).foldLeft(15)(_ - _)

res17: Int = 0

过程分析:

15-1=14

14-2=12

12-3=9

9-4=5

5-5=0

foldRight: 元素从右到左,第一个元素和指定元素计算,第二各元素和前面的结果进行运算,一直到最后

scala> List(1,2,3,4,5).foldRight(15)(_ - _)

res18: Int = -12

过程分析:

5-15=-10

4-(-10)=14

3-14=-11

2-(-11)=13

1-13=-12

 

六 定义函数括号问题

定义函数我们如果没有参数,可以不加括号,也可以加括号

但是定义的时候,没加,在调用的时候,就不要加,否则会报错;如果在定义的时候加那我们在调用的时候也加上

class Hello{

    var name= "nicky"

    defhello(){print("Hello,"+name)}

    def say =print(name+", welcome to you!")

    defgetName = name

}

val hello = new Hello

scala> hello.hello()

Hello,nicky

scala> hello.say

nicky, welcome to you!

scala> hello.say()

<console>:13: error: Unit does nottake parameters

      hello.say()

这里就是定义函数没加括号,但是调用的时候加括号了,然后就报错