详解 Scala 的函数式编程

一、函数基础

函数式是基于数学理论的函数概念,类似于 y = f(x)

1. 函数定义

1.1 语法
/*  函数结构:
    def funcName(param1 : type1, param2 : type2,...): type = { statement }
 */
def sum(x : Int, y : Int): Int = {
    return x + y
}
1.2 案例
object TestFunctionDefine {
    def main(args: Array[String]): Unit = {
        
        //  函数 1:无参,无返回值
        def test1(): Unit = {
        	println("1:无参,无返回值")
        }
        test1()
        
        //  函数 2:无参,有返回值
        def test2(): String = {
        	return "2:无参,有返回值"
        }
        println(test2())
        
        //  函数 3:有参,无返回值
        def test3(s: String): Unit = {
        	println("3:有参,无返回值" + s)
        }
        test3("Hi")
        
        //  函数 4:有参,有返回值
        def test4(s: String): String = {
        	return s + "4:有参,有返回值"
        }
        println(test4("hello "))
        
        //  函数 5:多参,无返回值
        def test5(name: String, age: Int): Unit = {
        	println(s"${name}今年${age}岁")
        }
        test5("阿豪", 18)
        
        //  函数 6:多参,有返回值
        def test6(a: Int, b: Int): Int = {
            return a + b
        }
        println(test6(10, 20))
        
    }
}

2. 函数VS方法

  • 概念:

    • 函数:为完成某一功能的程序语句的集合 (区别于函数式编程的函数)
    • 方法:类中的函数
    object TestFunctionAndMethod {
        def main(args: Array[String]): Unit = {
            // 定义函数
            def sayHi(name : String): Unit = {
                println("hi," + name)
            }
            
            // 函数调用
            sayHi("张三")
            
            // 方法调用
            TestFunctionAndMethod.sayHi("李四")
            
            // 获取方法返回值
            val result = TestFunctionAndMethod.sayHello("王五")
            println(result)
            
        }
        
        // 定义方法
        def sayHi(name : String): Unit = {
            println("Hi," + name)
        }
        
        // 定义有返回值的方法
        def sayHello(name : String): String = {
            return "Hello," + name
        }
    }
    
  • 语法:

    • Scala 语言可以在任何的语法结构中声明任何的语法
    • 函数没有重载和重写的概念;方法可以进行重载和重写
    • Scala 中函数可以嵌套定义
    object TestFunctionAndMethod {
        // 方法可以进行重载和重写,程序可以执行
        def main(): Unit = {
            
        }
        
        def main(args: Array[String]): Unit = {
            // Scala 语言可以在任何的语法结构中声明任何的语法
            import java.util.Date
            new Date()
            
            // 函数没有重载和重写的概念,程序会报错
            def test(): Unit = {
            	println("无参,无返回值")
            }
            test()
            def test(name:String): Unit = {
            	println()
            }
            
            // Scala 中函数可以嵌套定义
            def test2(): Unit = {
                def test3(name:String): Unit = {
                	println("函数可以嵌套定义")
                }
            }
        }
    }
    

3. 函数参数

object TestFunctionParam {
    def main(args: Array[String]): Unit = {
        // 1.可变参数,且可变参数一般放在参数列表的最后
        def f1(s: String*): Unit = { // 可变参数底层是 ArrayBuffer[String]
            println(s)
        }
        f1() // WrappedArray()
        f1("tom") // WrappedArray(tom)
        f1("tom", "jerry") // WrappedArray(tom, jerry)
        
        def f2(s1: String, s2: String*): Unit = {
            println("s1:" + s1 + ", s2:" + s2)
        }
        f2("jack")
        f2("jack", "rose", "james")
        
        // 2.参数默认值,有默认值的参数放置在参数列表的后面
        def f3(name: String, school: String = "HighSchool"): Unit = {
            println("my name is " + name + ", my school is " + school)
        }
        f3("james")
        f3("james", "GoodUniversity")
        
        // 3.带名参数
        def f4(name: String = "abc", age: Int): Unit = {
            println(s"${name}今年已经${age}岁了")
        }
        f4("rock", 25)
        f4(age = 30, name = "curry")
        f4(age = 18)
        
    }
}

4. 函数至简原则

重点

1.4.1 要点
  • return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
  • 如果函数体只有一行代码,可以省略花括号
  • 返回值类型如果能够推断出来,那么可以省略(: 和返回值类型一起省略)
  • 如果有 return,则不能省略返回值类型,必须指定
  • 如果函数明确声明 Unit,那么即使函数体中使用 return 关键字也不起作用
  • Scala 如果期望是无返回值类型,可以省略等号
  • 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
  • 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
  • 如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
1.4.2 案例
object TestFunctionSimplify {
    def main(args: Array[String]): Unit = {
        // 函数标准语法
        def f0(name: String): String = {
            return name
        }
        println(f0("jack"))
        
        // 简化:
        // 1. return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
        def f1(name: String): String = {
            name
        }
        println(f1("jack"))
        
		// 2. 如果函数体只有一行代码,可以省略花括号
        def f2(name: String): String = name

        println(f2("jack"))
        
		// 3. 返回值类型如果能够推断出来,那么可以省略(: 和返回值类型一起省略)
        def f3(name: String) = name

        println(f3("jack"))
        
		// 4. 如果有 return,则不能省略返回值类型,必须指定
        /* error:
        	def f4(name: String) = {
           		return name
           	}
         */
        
		// 5. 如果函数明确声明 Unit,那么即使函数体中使用 return 关键字也不起作用
        def f5(name: String): Unit = { // 函数返回值为 ()
            return name // 失效
        }
        println(f5("jack")) // ()
        
		// 6. Scala 如果期望是无返回值类型,可以省略等号
        def f6(name: String) { 
            println(name)
        }
        println(f6("jack"))
        
		// 7. 如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
        def f7() { 
            println("jack")
        }
        f7()
        f7
        
		// 8. 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
        def f8 { 
            println("jack")
        }
        // f8() // 报错
        f8
        
		// 9. 如果不关心名称,只关心逻辑处理,那么def和函数名可以省略,即匿名函数或 lambda表达式
        (name: String) => { println(name) }
        
    }
}

二、函数高级

1. 匿名函数

1.1 介绍
  • 概念:匿名函数是没有名字的函数,也称为 lambda 表达式

  • 语法:

    (param1: type1, param2: type2,...) => { statement }
    
1.2 案例
object TestLambda {
    def main(args: Array[String]): Unit = {
        
        // 单参数案例:
        val fun = (name: String) => { println(name) }
        fun("jack")
        
        def f(func: String => Unit): Unit = {
            func("jack")
        }
        f((name: String) => { println(name) })
        
        // 简化:
        // 1. 参数的类型可以省略,会根据形参进行自动的推导
        f((name) => { println(name) })
		// 2. 类型省略之后,若只有一个参数,则圆括号可以省略,否则不能省略圆括号
        f( name => { println(name) } )
		// 3. 匿名函数如果只有一行,则大括号也可以省略
        f( name => println(name) )
		// 4. 如果参数只出现一次,则参数省略且后面参数可以用_代替
        f( println(_) )
        // 5. 如果方法体只是某个方法的调用,则只需要传入调用的方法名
        f( println )
        
        
        // 多参数案例:
        def f2(func: (Int, Int) => Int): Int = {
            func(1,2)
        }
        
        f2((a: Int, b: Int) => a + b)
        f2((a: Int, b: Int) => a - b)
        
        f2((a, b) => a + b)
        f2((a, b) => a - b)
        
        f2( _ + _ )
        f2( _ - _ )
        
        f2((a, b) => b - a)
        f2( -_ + _ )
        
    }
}
1.3 练习
/**
练习 1:定义一个匿名函数,并将它作为值赋给变量 fun。函数有三个参数,类型分别为 Int,String,Char,返回值类型为 Boolean。要求调用函数 fun(0, “”, ‘0’) 得到返回值为 false,其它情况均返回 true。
练习 2:定义一个函数 func,它接收一个 Int 类型的参数,返回一个函数(记作 f1)。它返回的函数 f1,接收一个 String 类型的参数,同样返回一个函数(记作 f2)。函数 f2 接收一个 Char 类型的参数,返回一个 Boolean 的值。要求调用函数 func(0) (“”) (‘0’)得到返回值为 false,其它情况均返回 true
*/
object Practice {
    def main(args: Array[String]): Unit = {
        // 练习 1
        val fun = (a: Int, b: String, c: Char) => { if(a == 0 && b == "" && c == '0') false else true }
        println(fun(0, "", '0'))
        println(fun(1, "", '1'))
        
        println("==========================")

        // 练习 2
        def func(a: Int): String => (Char => Boolean) = {
            def f1(s: String): Char => Boolean = {
                def f2(c: Char): Boolean = {
                    if(a == 0 && s == "" && c == '0') false else true
                }
                f2 _
            }
            f1
        }
        
        println(func(0)("")('0'))
        println(func(1)("")('1'))
        
        // 练习 2 匿名函数写法
        def func1(a: Int): String => (Char => Boolean) = {
            s => c => if(a == 0 && s == "" && c == '0') false else true
        }
        println(func1(0)("")('0'))
        println(func1(1)("")('1'))
        
        // 练习 2 柯里化写法(推荐)
        def func2(a: Int)(s: String)(c: Char): Boolean = {
            if(a == 0 && s == "" && c == '0') false else true
        }
        println(func2(0)("")('0'))
        println(func2(1)("")('1'))
        
    }
}

2. 高阶函数

2.1 特性
  • 函数可以作为值进行传递

    object TestHighLevelFunc {
        def main(args: Array[String]): Unit = {
            def f(n: Int): Int = {
                println("f调用")
                n + 1
            }
            
            def ff(): Int = {
                println("ff调用")
                1
            }
            
            val r1 = f(11) // 返回值
            val f1 = f _ // 函数 f 整体赋值给函数 f1
            val f11: Int => Int = f // 函数 f 整体赋值给函数 f1
            
            val r2 = ff // 返回值
            val ff1 = ff _ // 函数 ff 整体赋值给函数 ff1
            val ff2: () => Int = ff // 函数 ff 整体赋值给函数 ff2
            
        }
    }
    
  • 函数可以作为参数进行传递

    object TestHighLevelFunc {
        def main(args: Array[String]): Unit = {
            def calculator(func: (Int, Int) => Int, a: Int, b: Int): Int = {
                func(a, b)
            }
            
            def add(a: Int, b: Int): Int = {
                a + b
            }
            
            val result = calculator(add, 12, 18)
            println(result)
            
            
            // 匿名函数
            println(calculator((a,b)=>a + b, 12, 18))
            println(calculator(_ + _, 12, 18))
            
        }
    }
    
    
  • 函数可以作为函数返回值返回

    object TestHighLevelFunc {
        def main(args: Array[String]): Unit = {
            def outF(): Int => Unit = {
                def inF(a: Int): Unit = {
                    println("inF调用 " + a)
                }
                // inF _
                inF // 将函数整体作为返回值返回
            }
            
            val result = outF() // inF 函数的引用地址值
            println(outF()(10)) // 最终函数调用
            
        }
    }
    
    
2.2 案例
object FunctionPractice {
    def main(args: Array[String]): Unit = {
        val array: Array[Int] = Array(1, 2, 3, 4)
        
        // 1. 模拟 Map 映射
        def mapOP(array: Array[Int], op: Int => Int): Array[Int] = {
            for(elem <- array) yield op(elem)
        }
        
        def add(elem: Int): Int = {
            elem + 1
        }
        
        val res1 = mapOP(array, add)
        val res2 = mapOP(array, _ + 1) // 匿名函数传参
        println(res1.mkString(","))
        println(res2.mkString(","))
        
        // 2. 模拟 Filter 过滤
        def filterOP(array: Array[Int], op: Int => Boolean): Array[Int] = {
            for(elem <- array if op(elem)) yield elem
        }
        
        def filterFun(elem: Int): Boolean = {
            if(elem % 2 == 0) true else false
        }
        
        val res3 = filterOP(array, filterFun)
        val res4 = filterOP(array, _ % 2 == 0) // 匿名函数传参
        println(res3.mkString(","))
        println(res4.mkString(","))
        
        // 3. 模拟 Reduce 聚合
        def reduceOP(array: Array[Int], op: (Int, Int) => Int): Int = {
            var init: Int = array(0)
            for (index <- 1 until arr.length) {
            	init = op(init, arr(index))
            }
            init
        }
        
        def multiply(a: Int, b: Int): Int = {
            a * b
        }
        
        val res5 = reduceOP(array, multiply)
        val res6 = reduceOP(array, _ * _)
        
        println(res5)
        println(res6)
        
    }
}

3. 闭包

如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包

object TestClosure {
    def main(args: Array[String]): Unit = {
 		def func(a: Int): String => (Char => Boolean) = {
            def f1(s: String): Char => Boolean = {
                def f2(c: Char): Boolean = {
                    if(a == 0 && s == "" && c == '0') false else true
                }
                f2 _
            }
            f1
        }       
    }
}

/*
	在正常情况下,上述嵌套函数在调用的过程中由于外层函数先调用进行压栈和弹栈,所以外层函数的局部变量随着栈空间释放掉,从而导致内层函数调用时无法再获取到外层函数的局部变量,导致执行失败
	而 scala 的闭包是将外层函数和内层函数整体打包成一个对象存储在堆内存中,从而内层函数在调用时可以从线程共享的堆内存中获取到外层的局部变量正常执行
*/

4. 函数柯里化

  • 概念:把一个参数列表的多个参数,变成多个参数列表

  • 案例:

    object TestCurrying {
        def main(args: Array[String]): Unit = {
            // 闭包
            def add(a: Int): Int => Int = {
                def f(b: Int): Int = {
                    a + b
                }
                f
            }
            // 匿名函数简化
            def add1(a: Int): Int => Int = a + _
            
            // 柯里化可以简化闭包写法
            def add2(a: Int)(b: Int): Int = a + b
            
        }
    }
    

5. 递归

  • 概念:一个函数/方法在函数/方法体内又调用了本身,称为递归调用

  • 条件:

    • 方法调用自身
    • 方法必须要有跳出的逻辑
    • 方法调用自身时,传递的参数应该有规律
    • Scala 中的递归必须声明函数返回值类型
  • 案例:

    object TestRecursion {
        def main(args: Array[String]): Unit = {
            // 计算阶乘
            
            /* 普通递归实现
               缺点:在调用自身的同时前一次的调用必须等待(不能弹栈),从而导致大量占用Stack空间,
               进而引发 stack over flow
            */
            def fact(n: Int): Int = {
                if(n == 0) 1 else fact(n - 1) * n
            }
            println(fact(5))
            
            /*
            	尾递归优化实现
            	函数式编程语言都是支持尾递归
            	Scala 的注解 @tailrec 标注在方法上可以检验方法尾递归实现是否正确
            */
            def tailFact(n: Int): Int = {
                @tailrec
                def loop(n: Int, currRes: Int): Int = {
                    if(n == 0) currRes else loop(n - 1, currRes * n)
                }
                loop(n, 1)
            }
            println(tailFact(5))
            
        }
    }
    

6. 控制抽象

Java 只有值调用;Scala 既有值调用,又有名调用

object TestControlAbstract {
    def main(args: Array[String]): Unit = {
        // 1. 值调用:把函数计算后的值作为参数传递给另一个函数
        def f0(a: Int): Unit = {
            println("a: " + a)
            println("a: " + a)
        }
        
        def f(): Int = {
            println("f被调用")
            10
        }
        
        f0(f()) // f 被调用一次
        println("==========================")
        
        // 2. 名调用:将代码块作为参数传递给一个函数
        // => Int 表示返回值类型为 Int 的代码块 
        def f1(a: => Int): Int = {
            println("a: " + a)
            println("a: " + a)
        }
        
        f1(f()) // f 被调用两次
        
    }
}
/*
	自定义 while 循环
*/
object MyWhile {
    def main(args: Array[String]): Unit = {
        // 1. 普通 while 循环
        var n = 10
        while(n >= 1) {
            println(n)
            n -= 1
        }
        
        // 2. 使用匿名函数、柯里化、闭包、控制抽象和递归实现自定义 while
        // 解析 while 语法:相当于 func(param1)(param2),param1 和 param2 都是代码块参数
        def myWhile(condition: => Boolean)(op: => Unit): Unit = {
            if(condition) {
                op
                myWhile(condition)(op)
            }
        }
        
        var t = 10
        myWhile(t >= 1){ // 小括号可以省略
            println(t)
            t -= 1
        }
        
    }
}

7. 惰性加载

懒加载

  • 概念:当接收函数返回值的变量 (val) 被 lazy 修饰时,函数的执行将被推迟,直到首次使用该变量时函数才会执行。这样的函数也称为惰性函数。

  • 案例:

    object TestLazyLoad {
        def main(args: Array[String]): Unit = {
            // 正常加载
            val n1: Int = sum(10, 12)
            println("1. 函数调用")
            println("2. result: " + n1)
            println("4. result: " + n1)
            // 打印顺序:3 > 1 > 2 > 4
            println("========================")
            
            // 惰性加载
            // lazy 不能修饰 var 类型的变量
            lazy val n2: Int = sum(11, 12)
            println("1. 函数调用")
            println("2. result: " + n2)
            println("4. result: " + n2)
            // 打印顺序:1 > 3 > 2 > 4
        }
        
        def sum(a: Int, b: Int): Int = {
            println("3. sum 调用")
            a + b
        }
        
    }
    

相关推荐

  1. 详解 Scala 函数编程

    2024-05-25 21:14:44       11 阅读
  2. 5 scala函数编程简介

    2024-05-25 21:14:44       32 阅读
  3. scala05-函数编程02

    2024-05-25 21:14:44       20 阅读
  4. scala05-函数编程01

    2024-05-25 21:14:44       20 阅读
  5. Scala编程基础7:模式匹配、隐转换详解

    2024-05-25 21:14:44       9 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-05-25 21:14:44       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-05-25 21:14:44       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-05-25 21:14:44       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-05-25 21:14:44       18 阅读

热门阅读

  1. Pytorch-06 使用GPU加速计算

    2024-05-25 21:14:44       11 阅读
  2. Pytorch-07 完整训练测试过程

    2024-05-25 21:14:44       11 阅读
  3. c++翻转一个无符号数的二进制位

    2024-05-25 21:14:44       12 阅读
  4. C++11std::bind的简单使用

    2024-05-25 21:14:44       9 阅读
  5. el-select 组件获取整个对象

    2024-05-25 21:14:44       12 阅读
  6. K8S Secret管理之SealedSecrets

    2024-05-25 21:14:44       9 阅读
  7. c++入门

    c++入门

    2024-05-25 21:14:44      12 阅读
  8. 分布式和集群区别

    2024-05-25 21:14:44       7 阅读
  9. 华为校招机试 - 最久最少使用缓存(20240508)

    2024-05-25 21:14:44       11 阅读