scala基础【高阶函数编程】

一 高阶函数编程

什么是高阶函数编程,所谓的高阶函数,其实就是将函数当成一个类型来使用,而不是当成特定的语法结构。

1 将函数作为对象

函数也是一个对象

User user = new User
val f = ()=>{"zhangsan"}
def fun1(): Unit ={
      println("fun1...")
    }
    val f1 = fun1
    val f2 = fun1()

如果想将函数作为整体,而不是执行结果赋值给变量,需要使用一个特殊符号:下划线

    def fun1(): Unit ={
      println("fun1...")
    }
    val f1 = fun1 _
    val f2 = fun1()

    f1()

万物皆对象,但是对象都有类型,就意味着函数对象也有类型,f1的类型是Function,后面的0为参数个数,泛型为返回值类型。

函数独立使用时,参数声明没有个数限制;但是如果将参数作为对象给别人使用,那么函数参数声明的个数最多为22个。

val f2 : Function0[Unit] = fun1 _

函数类型还有另外一种写法:(输入参数类型) => 输出参数类型(推荐使用)

val f3 : () => Unit = fun1 _

使用下划线让函数作为对象使用的原因:代码中没有明确变量的类型,所以需要通过取值类型推断,反之,如果明确变量的类型为函数类型,那么下划线可以省略不写

def test1(name : String, age : Int):String = {
      "123"
    }
    val testVar : (String,Int) => String = test1

下划线的作用:

1.声明变量,但是不能访问
	val _ = "zhangsan"
2.将函数作为整体使用
	val f = fun1 _

2 将函数作为参数

在java中有以下表达

public void test(User user){}
//public void test( 函数类型 参数名称)

在scala中的实现

def test(f : ()=> Unit) : Unit ={
      f()
    }
    //函数对象将作为参数,传入test方法
    def fun1(): Unit = {
      println("我是函数对象")
    }
    //执行
    test(fun1)

输入两个Int,输出一个Int

    def test(f : (Int,Int) => Int): Unit = {
      val a = f(10,20)
      println(a)
    }
    //函数对象
    def fun1(a:Int , b:Int) : Int = {
      a + b
    }
    //执行
    test(fun1)
  }

函数对象负责计算的逻辑,是不是很有魅力!

匿名函数

当想实现加减乘除四个功能时,需要定义四个函数,有点复杂。在这个需求中函数名字不是很重要,因为不论叫什么,最终都会调用test函数中的f函数,所以可以简化成下面的形式,省略函数名。

	test(
      (a:Int , b:Int) => {
        a + b
      }
    )

想要修改功能,直接在参数位置改变运算符。

所以匿名函数主要应用于函数作为参数使用时。

上面的形式还是不简单,所以,匿名函数在使用时也可以使用至简原则

  • 如果函数体的逻辑代码只有一行,大括号可以省略,代码写在一行中
  • 如果参数的类型可以推断出来,则参数的类型可以省略
  • 如果参数只有一个,参数列表的小括号可以省略
  • 如果参数在使用时,按照顺序只使用了一次(注意减法),那么可以使用下划线代替参数,下划线类似于sql中的占位符

依次简化

	test(
      (a:Int , b:Int) => {
        a + b
      }
    )
    ---------------------------
	test(
      (a:Int , b:Int) => a + b
    )
    ---------------------------
	test(
      (a , b) => a + b
    )
    ---------------------------
	test(_ + _)

使用匿名函数重新定义以下功能

def main(args: Array[String]): Unit = {

    def test(f: (String) =>Unit): Unit = {
      f("zhangsan")
    }
    def fun(name:String): Unit ={
      println(name)
    }
    test(fun)
  }
    //省略函数名
    test(
      (name:String) => {
        println(name)
      }
    )
	//逻辑代码只有一行省略大括号
    test(
      (name:String) => println(name))
	//变量类型可以推断
    test((name) => println(name))
	//参数只有一个,且只顺序使用一次
	//此为匿名函数至简原则的最终版
    test(println(_))
	//此为有名函数的简化
    test(println)

匿名函数的使用(输入两个整数,但函数的计算逻辑不确定)

def main(args: Array[String]): Unit = {
    def test(x:Int, y:Int, f : (Int,Int) => Int): Unit = {
      val r = f(x,y)
      println(r)
    }
    test(10,20,_-_)	//完整语句为下一行
    test(10,20,(x:Int, y:Int) => {x + y})
  }

3 函数作为返回值

在java中可能出现下列情况

  /**
   * public User getUser(){
   *  return new User();
   * }
   */

将函数作为对象返回

    def test(): Unit ={
      println("funct(ion...")
    }

    def fun() = {
      //下划线:当成对象,直接返回
      test _
    }

    //f就是个function
    val f = fun _
    //使用ff接收f执行完返回的对象
    val ff = f()
    //执行这个对象
    ff()
  }

上面最后三行代码比较麻烦,可以进行简化。fun()返回一个对象o,o()再执行

fun()()

与下一行代码执行结果相同

test()

所以,将函数作为返回值的使用场景:一般应用于将内部函数在外部使用

不推荐使用这种方式自己定义类型

def main(args: Array[String]): Unit = {

    def outer() = {
      def inner(): Unit ={
        println("inner...")
      }
      inner _
    }
    
    outer()()
  }

闭包

当一个内部函数使用了外部变量,它会将这个变量包含到它的内部来使用,改变了这个变量的生命周期,将当前的代码形成了一个闭合的环境,这个环境称之为闭包环境,简称闭包,具体看下列例子:

def main(args: Array[String]): Unit = {
    def outer(a : Int) = {
      def inner(b : Int) : Int = {
        a + b
      }
      inner _
    }

    //println(outer(10)(20))
    val f = outer(10)	//若没有闭包,f的参数a会在执行完出栈,那么也就不能计算 a + b了
    //有闭包,outer在出栈前先将a留给inner
    val ff = f(20)
    println(ff)
  }

反编译结果

def main(args: Array[String]): Unit = {
    def test(a:Int): Unit ={
      def test(b:Int): Unit ={
        a + b
      }
    }
  }

将以上代码反编译,可以更加直观的看到闭包的效果

  private static final void test$2(int b, int a$1)
  {
    (a$1 + b);
  }
  private static final void test$1(int a) {
  }

何时会出现闭包:

  • 内部函数在外部使用
  • 将函数作为对象使用
  • 所有的匿名函数都有闭包

总结:只要改变了变量的生命周期,都会出现闭包

控制抽象

所谓控制抽象是参数类型不完整,则在传递参数时,参数也不是完整的:只要传递代码就可以,不需要完整的声明。

def main(args: Array[String]): Unit = {
	//正常的函数类型:()=>Unit
        def fun7(op: => Unit) = {
            op
        }
        fun7{
            println("xx")
        }
    }

可以采用控制抽象来设计语法

def main(args: Array[String]): Unit = {
  
    def myWhile(op: => Boolean) ={
      op
    }
    
    val age = 10
    myWhile(
      age < 20
    ) {
      println("XXX")
    } 
  }

如果有多行代码逻辑,可以将小括号变为大括号,上述代码等同于

val age = 10
    while (age < 20){
      println("XXX")
    }

在框架中使用较多

    Breaks.breakable{
      for(i <- 1 to 5){
        Breaks.break()
      }
    }

4 函数柯里化(Curry)

Curry,数学科学家,他发现了一个现象:当逻辑上没有关联的参数在执行时,需要等待对方都执行完成,才会进行下一步骤

def main(args: Array[String]): Unit = {
    def test(a:Int, b:Int): Unit ={
      for(i <- 1 to a ){  //耗时10min
        println(i)
      }
      for(j <- 1 to b){ //耗时20min
        println(j)
      }
    }
    val a = 10  //耗时10min
    val b = 20  //耗时20min
    test(a,b)   //耗时60min

  }

所以,他提出了一个解决方案:将无关的参数分离开,形成多个参数列表

    def test1(a:Int)(b:Int): Unit ={
      for(i <- 1 to a ){  //耗时10min
        println(i)
      }
      for(j <- 1 to b){ //耗时20min
        println(j)
      }
    }
    test1(a)(b)

5 递归函数

阶乘的scala实现方法

scala中要求递归函数必须明确声明返回值类型

关于递归一些注意的点:

  • 函数内部调用自身
  • 一定要有递归出口
  • 在调用时传递的参数之间有关系

关于递归常见的异常/错误:

  • StackOverflowError:栈滚动错误,目前分配给栈的内存都已经用完,栈已经压满了,如果有递归出口,但是若压栈次数太多,也会出现此错误
  • 栈内存溢出:和线程有关,每一个线程有一个独立的栈,若线程太多,就会出现此溢出

**递归:**执行会出现错误,因为执行到test()语句后,下面还有一句,没有执行完上一个test()方法不能出栈

    def test(): Unit ={
      test()
      println("XXXX")
    }

**尾(伪)递归:**不是真正的递归,由编译器进行优化,形成了while循环。可以正常执行,无限循环,执行到test1()后,上一个test1()执行完成,出栈,出栈之后,下一个test1()才进栈

def test1(): Unit ={
      println("XXX")
      test1()
    }
  }

反编译结果:

private final void test1$1()
  {
    while (true)
      Predef..MODULE$.println("XXX");
  }

6 惰性函数

函数的执行结果后续没有使用,那么这个函数就不执行

def main(args: Array[String]): Unit = {
        def fun9(): String = {
            println("function...")
            "zhangsan"
        }
        val a = fun9()		//若加载10000User对象
        println("----------")	//本句执行两分钟
        println(a)	//遍历1000个对象
    }

执行结果为function… ------------ zhangsan,缺点在于在两分钟内,没有遍历这10000个对象,使得可用内存减少,改进方法,当遍历时再去读取这些对象

def main(args: Array[String]): Unit = {
        def fun9(): String = {
            println("function...")
            "zhangsan"
        }
        lazy val a = fun9()
        println("----------")
        println(a)
    }

执行结果 ---------- function… zhangsan

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

OneTenTwo76

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值