大数据8_06_Scala函数式编程

7 函数式编程

main()方法的返回值类型Unit也可以省略,但前提是自动推断的类型一定要是Unit,不然main方法没有入口

object TestClosePackage2 {
  def main(args: Array[String]): Unit = {
    val name = "zhangSan"
    def test() = {
      println(name)
    }
    val f = test _		//返回函数的这个对象,调用的时候使用f()
    val f2 = test()		//返回函数的返回值,同时调用
    val f3 = test		//和test()一样,只是省略了()
    println(f)
    println(f2)
    println(f3)
      
    def fun1() = {
      println("fun1....")
      "zhang san"
    }
//    val f1 = fun1() //调用函数fun1()的同时,将返回值给f1
//    val f1 = fun1   //调用函数fun1的同时,将返回值给f1
    val f1 = fun1 _   //将函数fun1作为对象赋给f1,调用的时候使用f()
      
  }
}

7.1 基础函数式编程

[修饰符] def 函数名 ( 参数列表 ) [:返回值类型] = {
    函数体
}

private def test( s : String ) : Unit = {
    println(s)
}

函数 & 方法

  • Java中没有函数的概念,有方法的概念

  • Scala:在类中声明的函数就称为方法,其他地方声明的就是函数。

因为方法声明在类中,所以就要符合方法的约束和规则;方法可以重载和重写。

方法可以重载和重写,函数没有!

object TestFun1 {

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

    //函数式编程:
    //1 无参无返回值
    def test1(): Unit = {
      val name = "Tom"
      val age = 23
      println(s"this is test 1 ${name} and this is test1 ${age}")

    }

    //2 无参有返回值
    def test2(): String = {
      return "this is function2"
    }

    //3 有参无返回值
    def test3(username: String, age: Int): Unit = {
      val username1 = username
      val age1 : Int = age

      println(
        s"""
           |your name is ${username1}
           |your age is ${age1}
           |""".stripMargin)
    }

    //4 有参有返回值
    def test4(name :String, age:Int, money :Double) : Double = {
      if (name.equals("Tom")){
        return age * money
      }
      return money - age
    }
//    def test4(count :Int) :Unit = {
//
//    }

  }
  //方法可以重载;凡是函数不能重载
  def method1(gender :Char) :Boolean = {
    if(gender == 'M'){
      return true
    }
    return false
  }
  def method1(gender:Char, money :Double): Unit ={
    if (gender == 'M'){
      printf("money is :%s\n", money)
    }
  }
}

7.2 函数参数

①可变参数
  • 可变参数加上*号;类似于Java的…
  • 可变参数不能放在参数列表的前面,一般放在参数列表的最后
object Test3 {
  def main(args: Array[String]): Unit = {

    //1 可变参数
    def fun1(name: String): Unit = {
      println(name)
    }
    //    fun1()  //错
    fun1("Tom")
    //    fun1("Tom", "Jerry")  //对

    def fun2(name: String*): Unit = {
      fun2()
      fun2("Tom")
      fun2("Tom", "Jerry")
    }

    //可变参数不能放置在参数列表的前面,一般放在参数列表的最后
    //错误的:
    //    def fun3(name : String*, age : Int) :Unit = {
    //    }
    //正确的:
    def fun4(age: Int, name: String*): Unit = {
      println(age)
      println(name)
    }
  }
}
  • 函数的参数有多少个呢?

    函数的参数可以有无限多,但是将函数给其他人使用,这个时候参数的个数有限制,不能多于22个。

②参数默认值
object Test3 {
  def main(args: Array[String]): Unit = {
      //给参数设置默认值
    def fun6(password: String = "123456"): Unit = {
      println(password)
    }
    fun6(null)  //null
      //设置了默认值那就让参数列表为空就好了
    fun6()  //123456
      
     //当带有默认值的参数放在参数列表的前面,
   	def fun7(password: String = "123456", user: String): Unit = {
      println(password + ", " + user)
    }
      //①调用函数的时候,不能省略有默认值的参数
    fun7("abc", "Tom") //abc, Tom
	//当带默认值的参数放在了参数列表的后面。
      
    def fun8(username: String, password: String = "ROOT"): Unit = {
      println(username + "=== " + password)
    }
      //②调用函数的时候,可以声明后面带有默认值的参数
    fun8("Jerry") 
   
  }
}
③ 带名参数

按照指定名称把参数放到指定的顺序位置

object ScalaFunction {
    def main(args: Array[String]): Unit = {
        //带名参数
        def fun9(name :String = "Tom", school : String) : Unit = {
          println(name + "--==" + school)
        }
        fun9(school = "bigdata")
    }
}

7.3 函数至简原则

所以这里的至简原则,简单来说就是:能省则省。

①省略return关键字

函数可以将最后一行代码的结果作为函数的返回值

    //1 省略return关键字
    //TODO 1 函数可以将最后一行代码的结果作为函数的返回值
    def fun1(): String = {
      "zhang san"
    }

    val name = fun1()
    println(name)
②省略返回值类型

如果返回值类型能够确定,那么类型可以省略

    //TODO 2 如果返回值能够确定,那么类型也可以省略
    def fun2() = {
      "zhang san"
    }

    def fun3() = {
      var age = 30
      if (age < 20)
        10
      else
        "string"
    }

省略返回值类型的话,看最后一行的类型,如果最后一行不能判断是什么类型,那就是他的父类型。

image-20201112235117655

image-20201112235210687

image-20201112235407044

③ 省略大括号

如果函数逻辑代码只有一行,那么大括号可以省略

    //TODO 3 如果函数逻辑代码只有一行,那么大括号可以省略
    def fun4() = "zhang san"

    def fun5() = println("li si")
④省略小括号

如果参数列表没有参数,那么小括号可以省略

    //TODO 4 如果参数列表没有参数,那么小括号可以省略
    def fun6 = {
      if (8 > 2)
        "Bigger"
    }
⑤省略等号

5.1 如果函数的返回值为Unit 那么可以和 = 号一起省略

但是需要注意,print(fun1)的结果是(); print(fun1 _)的结果是将函数作为一个对象返回,结果是地址

//    def fun1(): String = {
//      return "Tom and Jerry"
//    }
	def fun1() {"Tom and Jerry"}

5.2 如果函数体中明确有return语句,那么返回值类型不能省略

//    def fun1(): String = {
//      return "Tom and Jerry"
//    }   
	def fun1(): String = {
      return "Tom and Jerry"
    }

5.3 如果函数的返回值类型是Unit,那么函数体内的return关键字不起作用

也就是说:print(fun1)的结果是()

//    def fun1(): String = {
//      return "Tom and Jerry"
//    }  
    def fun1() :Unit = {
      return "Tom and Jerry"
    }
   	println(fun1) 	//()
⑥省略名称和关键字(匿名函数)

匿名函数,因为没有名字所以无法直接调用,需要赋值给其他的变量来调用

如果函数声明时省略了参数列表,那么调用时也不能加参数列表

    // TODO 6 省略名称和关键字也就是:匿名函数
    () => {
      println("Tom")
    }
	//带参数的匿名函数
//    (name :String) =>{
//      println(name)
//    }

	// 把匿名函数的结果传给一个变量。
    val result: String => Unit = (name: String) => {
      println(name)
    }
    result("Tom") //Tom

    val unit: () => Unit = () => {
      println("Jerry....")
    }
    unit()	//Jerry....

    //这是因为匿名函数的返回值类型时Unit,函数体内的return不起作用
    val function: String => Nothing = (name: String) => {
      return name
    }
    function("Tom") //没有结果输出
  • 直接写匿名函数:

image-20201113233815938

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

//    def fun1(): String = {
//      return "Tom and Jerry"
//    }

    //TODO 1 省略return关键字
//    def fun1(): String = {
//      "Tom and Jerry"
//    }

    // TODO 2 如果能够确定函数的返回值类型,那么类型可以省
//    def fun1() = {
//      "Tom and Jerry"
//    }

    // TODO 3 如果函数的逻辑代码只有一行,那么可以省略大括号
//    def fun1() = "Tom and Jerry"

    // TODO 4 如果函数的参数列表没有参数可以省略小括号
//    def fun1 = "Tom and Jerry"

    // TODO 5 如果函数的返回值类型为Unit 那么可以和=号一起省略
    //但是print(fun1)的结果是(); print(fun1 _)的结果是将函数作为一个对象返回,结果是地址
//    def fun1() {"Tom and Jerry"}

    // TODO 5.1 如果函数体中明确有return语句,那么返回值类型不能省略
    def fun1(): String = {
      return "Tom and Jerry"
    }

    // TODO 5.2 如果函数的返回值类型明确是Unit,那么函数体中即使有return关键字也不起作用。
    //print(fun1)结果为()
//    def fun1() :Unit = {
//      return "Tom and Jerry"
//    }

    println(fun1)
    
  }
}

7.4 高级函数编程

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

① 函数作为值
object TestFunction_Hell {
  def main(args: Array[String]): Unit = {
    //TODO scala 万事万物皆对象,所以函数也是对象,所以可以给其他人使用
    def fun1() : Unit = {
      println("zhang san")
    }
    //将函数作为对象赋值给变量,这个变量其实就是函数
    //函数有类型,类型的名字就是Function
    //函数类型在Scala中默认识别22个参数,如果函数参数超过22,就无法使用

    //TODO 注意点1!
    val f1 = fun1() //调用fun1(),同时将结果赋值给f1,结果是()
//    f1() //报错
    println(f1) // ()

    //TODO 注意点2!
    val f11 : Function0[Unit] = fun1  //这是将函数作为一个对象赋值给一个变量
    //上面的其实是完整的写法是下面
    val f12 : ()=> Unit = fun1

    f11() //调用函数要使用函数名+()
    f12()

    //TODO 注意点3!
    //如果想要让函数作为对象类使用,但是又不想声明函数类型,那么可以采用特殊符号
    val f3 = fun1 _
    f3()
    
  }
}
② 函数作为参数

将函数作为参数来使用一般使用匿名函数

object TestSeniorFun2 {
  def main(args: Array[String]): Unit = {
    // TODO 将函数作为参数来使用
    //  一般情况使用匿名函数
    def fun1(f : ()=> Unit) : Unit = {
      f()
    }

    def test() = {
      println("Test....")
    }
    //方式1:直接将test()这个函数传给,fun1
//    fun1(test)
    //方式2:将test()这个函数赋值给一个变量,将变量传给fun1
    val f : ()=> Unit = test
    fun1(f)
      
      
    // TODO 使用匿名函数
    def fun2(f: (Int, Int) => Int) = {
      f(10, 20)
    }
//    def test2(i: Int, j:Int) = {
//      i * j
//    }
//    def test2(i: Int, j: Int) = {
//      i + j
//    }
//
//    val result = fun2(test2)
    //可以直接定义一个匿名函数作为参数传到fun2中
    val result = fun2((i:Int, j: Int) => {i + j})
    println(result)  
  }
}
③ 函数作为返回值
object TestSeniorFun33 {
  def main(args: Array[String]): Unit = {
    def fun(i: Int) : Int = {
      i * 2
    }
    
    //TODO: 将函数作为返回值使用
    def fun2() = {
      fun _
    }
    println(fun2()(10))

  }
}

噩梦难度:

object TestFunction_Hell_6 {
  def main(args: Array[String]): Unit = {
    //TODO 将函数作为返回值返回

    def fun(): Unit = {
      println("fun....")
    }

    def test() = {
      fun //把函数的结果返回 这样test()的类型就是Unit
      //      fun _ //把函数当作整体返回,这样test()的类型就是()=>Unit
    }

    //如何调用呢?

    //    val unit = test()
    //    unit()

    //    val test1 = test
    //    test1()

    //    test()()
    test()

    //TODO 将函数作为返回值返回
    //  一般的应用场合为将内部的逻辑给外部使用
    //如果将函数返回,那么一般省略返回值类型,应用自动类型推断
    def outer() = {
      println("outer...")
      def inner() = {
        println("inner...")
      }
      inner _
    }

    outer()()

    //oper(10)(10)(_+_) = 20
    //oper(10)(10)(_*_) = 100
    //oper(10, 10, _+_) = 20

    def oper(x: Int) = {
      def middle(y: Int) = {
        def inner(f: (Int, Int) => Int): Int = {
          f(x, y)
        }
        inner _
      }
      middle _
    }
    println(oper(10)(20)((x: Int, y: Int) => x + y)) //30
    println(oper(10)(20)(_ + _)) //30
  }
}
④ 匿名函数
object TestSeniorFun3 {
  def main(args: Array[String]): Unit = {
    def fun(f : (String) => Unit) : Unit = {
      f("zhang san ")
    }
    def test(name :String) : Unit = {
      println(name)
    }

//    fun(test)
    //TODO :使用匿名函数
//    fun((name:String) => {println(name)})   //zhang san
    //如果匿名函数的逻辑代码只有一行,那么大括号可以省略
//    fun((name : String) => println(name))
    //如果参数列表的类型可以推断出来,那么参数类型可以省略
//    fun((name) => println(name))
    //如果参数列表中的参数只有一个 ,那么小括号可以省略
//    fun(name => println(name))
    //如果在函数体中,参数只按照顺序使用了一次,那么参数就可以省略
    //使用下划线代替这个参数
    fun(println(_))
  }
}
object TestLazyFunction {
  def main(args: Array[String]): Unit = {

    def fun(f:(String, Int) => Unit) = {
      f("Tom", 23)
    }
    //匿名函数
    fun((x : String, y: Int) => {println(s"name=${x}, age=${y}")})
  }
}
⑤ 地狱般难度-补充
object Test3 {
  def main(args: Array[String]): Unit = {

    def fun(a: Int, b: Int, f: (Int, Int) => Int) = {
      val result = f(a, b)
      println(result)
    }


    fun(10, 20, (i: Int, j: Int) => {
      i + j
    })  //30

    fun(10, 20, (i: Int, j: Int) => i+j)  //30

    fun(10, 20, (i, j) => i + j)  //30

    fun(10, 20, _+_)  //30

    def test(a : Int, b: Int, f: (Int, Int) => Int) = {
      val result = f(a, b)
      println(result)
    }

//    test(10, 20, (i: Int, j: Int) => {i - j})

    test(10, 20, _-_)//-10,但是这个时候i和j的顺序就不能变,两个下划线按照顺序依次表示i和j
  }
}
⑥ 闭包

问题来了:oper(10)方法执行完了,方法就弹出栈,那么r1(20)中的x+y的x呢?

scala2.12之前采用的是匿名函数类实现闭包效果

spark3.0框架判断闭包的代码就发生了该百年

//TODO 一个函数使用了外部的变量,然后改变了这个变量的生命周期的操作
//  这样的操作称之为“闭包”,inner本来没有x=10(因为已经弹出栈了)但是为了能够使用,把它放在
//  inenr()方法的内部,形成一个闭合的环境,称为闭包环境。
//将不能直接使用的变量包含到当前函数的内部,改变生命周期,形成闭合的环境,称为闭包环境

    //TODO  判断是否存在闭包
    //1 函数使用的变量有没有改变生命周期
    //2 函数有没有当成对象给别人使用
    //3 所有的匿名函数都有闭包。因为匿名函数都是给别人使用的。
object TestClosePackage {
  def main(args: Array[String]): Unit = {

    def oper(x: Int) = {
      def inner(y: Int) = {
        x + y
      }

      inner _
    }
    //    println(oper(10)(20))
	//TODO 一个函数使用了外部的变量,然后改变了这个变量的生命周期的操作
    //  这样的操作称之为“闭包”,inner本来没有x=10(因为已经弹出栈了)但是为了能够使用,把它放在
    //  inenr()方法的内部,形成一个闭合的环境,称为闭包环境。
    //将不能直接使用的变量包含到当前函数的内部,改变生命周期,形成闭合的环境,称为闭包环境
    //也可以写成这样:
    val r1 = oper(10)
    val r2 = r1(20)
    println(r2)

  }
}

image-20201114013455221

image-20201114014048783

判断存不存在闭包?

object TestClosePackage2 {
  def main(args: Array[String]): Unit = {
    val name = "zhangSan"
    def test() = {
      println(name)
    }

//    test()//这样就没有闭包

    val f = test _
    f() //这样就有闭包

    //TODO  判断是否存在闭包
    //1 函数使用的变量有没有改变生命周期
    //2 函数有没有当成对象给别人使用
    //3 所有的匿名函数都有闭包。因为匿名函数都是给别人使用的。
  }
}

思考问题?没有使用外部变量还能称为闭包码?

是有可能的,没有改变别人的生命周期,有可能改变自己的生命周期

⑦ 抽象控制

抽象控制:将代码作为参数进行传递,一般应用于框架,实现特殊语法。

Scala支持把一段逻辑传给别人来用

object TestControlAbstract {
  def main(args: Array[String]): Unit = {
    //这样是给oper函数传进入一个函数
    def oper(f : (Int) => Unit) = {
      f(10)
    }

    //这样是给oper1函数传进入一段代码;这就是控制抽象
    def oper1(f : => Unit) = {
      f
    }

    //那么怎么去调用控制抽象的这个函数呢?
    oper1(
      println("This is a control abstract")
    )
    
    //也可以用大括号:
    oper1{
      for(i <- Range(0, 5)){
        println(i)
      }
    }
  }
}
⑧ 柯里化

柯里化:其实就是将复杂的多个参数简单化,==> 声明为多个参数列表

柯里化,用一句话解释就是,把一个多参数的函数转化为单参数函数的方法。柯里化收的函数是分步执行的,第一次调用返回的是一个函数,第二次调用的时候才会进行计算。起到延时计算的作用,通过延时计算求值,称之为惰性求值。

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

    //TODO 函数柯里化
    val i = 10 //10min
    val j = 20 //20min

    def test(i: Int, j: Int) = {
      for (k <- 1 to i) {
        println(k) //10min
      }

      for (m <- i to j) {
        println(m) //20min
      }
    }
    //i和j完全独立没有任何关系
    test(i, j) //60min
    

    //将复杂的参数简单化,声明多个参数列表,这种表达形式就是柯里化
    def test1(i: Int)(j:Int) = {

    }
    test1(i)(j)	//50min
  }
}
⑨ 惰性函数

当声明为lazy的函数,函数的执行将推迟,直到我们首次对此取值,该函数才会执行。这种函数称为惰性函数。

就比如, val f1 = fun1()如果fun1返回的是10000个对象,但是在逻辑的最后一行才用到了f1这个变量,那么在使用之前要一直占用了10000个对象的内存,就很占用资源。所以才出现了惰性函数。

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

    def fun1() = {
      println("fun1....")
      "zhang san"
    }
	//调用函数fun1()的同时,将返回值给f1
    val f1 = fun1() 	//1首先调用fun1(),打印fun1....
	
    println(f1)		//2 然后将fun1()的return结果赋给f1变量,打印“zhang san”

	//延迟加载(分页查询)
    lazy val f1 = fun1() 	//使用lazy修饰的函数,将会推迟执行,直到

  }
}
⑩ 递归
  • 递归:函数内部调用自身

  • 函数应该存在跳出递归的逻辑

    有跳出的逻辑,也有可能出现栈溢出,栈内存中的小格子不够了

  • 函数在传递参数时应该有规律

  • 递归的方法必须明确声明返回值类型,不能省略!

object TestRecursion {
  def main(args: Array[String]): Unit = {
    // TODO 递归函数
    //  1 函数内部调用自身
    //  2 函数应该存在跳出递归的逻辑
    //  3 函数在传递参数时应该有规律
    //  4 递归的方法必须明确声明返回值类型,能不能省略
    //因为能省返回值类型的前提是编译器能够推断出类型,但是如果不写的话,递归调用的test因为
    //省略了类型,所以它不知道是什么类型!

    //有跳出的逻辑,也有可能出现栈溢出,栈内存中的小格子不够了

    println(test(5))
    //求5的阶乘
    def test(num : Int): Int = {
      if (num <= 1)
        1
      else
        num * test(num - 1)
    }
  }
}

尾递归

尾递归(伪递归)

递归,在调用的时候,因为方法没有执行完,会一直有方法压栈,当栈溢出了就会报StackOverflowError

尾递归,在调用的时候,方法其实已经执行完了,可以把执行完的方法先弹出去,这样就不会一直压栈而导致栈溢出,Scala编译器将尾递归编译成了while死循环

image-20201115195116293

  def main(args:Array[String]): Unit = {
    //TODO 递归
    def test() : Unit = {
      test()
      println("recursive....")
    }
    //TODO 尾(伪)递归
    def test1 (): Unit  = {
      println("tail recursive....")
      test1()
    }
    test()// 递归,栈溢出就会报错了
    test1()// 尾递归,不会报错,scala会优化成while死循环
  }
}

Scala编译器在编译的时候,会将尾递归编译成while死循环

image-20201115195348985

java中的内存操作

image-20201115191528363

  • StackOverflowError 栈滚动错误,栈溢出。栈溢出 != 栈内存溢出

  • 栈溢出

    栈有一个深度的概念每个方法会压栈,是以栈帧的方式存在栈内。如果栈是1M,栈帧1K的话,那么只能压1024个栈帧,如果是递归一直调用自己,超过1024个就会出现栈溢出!

  • 栈内存溢出

    什么时候会出现栈内存溢出?

    Thread,java虚拟机会给每个线程分配一个栈内存,一个线程分配一个栈。

    如果一个栈式5M,要是有1024个线程,就需要5个G的内存,Java虚拟机默认占用多少内存?

    • 占可用内存的1/64;最大占可用内存的1/4
函数到底是什么?
  • 函数在编译的时候,就是将这个方法加上了$+编号,并且声明为private static final
  • 方法就是java中的成员方法

image-20201115205129456

object TestWhatIsFunction {
  def main(args: Array[String]): Unit = {
    // TODO 函数到底是什么?

    // 1 函数
    //编译时,会编译为private static final方法,方法名和函数名不一样,增加了后缀$
    //私有的不能在其他的类中调用。

    // 2 方法
    //编译时,会编译为java的成员方法
    //成员方法可以在其他类中调用

    def test()= {
      println("test1....")

      def test() = {
        println("test2....")
      }
    }

    test()  //test1....
    
  }
  def test() = {
    println("test3....")
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

最佳第六六六人

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

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

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

打赏作者

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

抵扣说明:

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

余额充值