Scala
函数(Function)
概述
将一段逻辑进行封装便于进行重复使用,被封装的这段逻辑就是函数。在Scala中,必须通过
def
来定义函数基本语法
def 函数名(参数列表) : 返回值类型 = { 函数体 return 返回值 }
案例
// 案例:定义函数计算两个整数的和 // 标准写法 def sum(a: Int, b: Int): Int = { return a + b } // 因为在Scala中,所有的结构都必须有返回值 // 所以在函数中,如果不指定,默认会将最后一行的计算结果作为返回值 // 也因此可以省略return不写 def sum(a: Int, b: Int): Int = { a + b } // 如果函数体只有一句话,那么此时也可以省略{}不写 def sum(a: Int, b: Int): Int = a + b // 参数类型非常明确,并且可以由计算结果来确定返回值类型 // 那么也就意味着此时可以推导出结果类型,因此可以省略返回值类型不写 def sum(a: Int, b: Int) = a + b
练习:定义一个函数,判断一个数字是否是质数
package com.fesco.method object MethodExer1 { def main(args: Array[String]): Unit = { println(isPrime(19)) println(isPrime(25)) } // 判断数字是否是质数 /* 定义函数,明确问题: 1. 是否需要参数 - 是否有未知量来参与运算,需要在调用函数的时候传入这个数据 2. 明确结果类型 - 判断是不是的问题,那么结果类型应该是布尔值 3. 明确计算逻辑 */ def isPrime(n: Int): Boolean = { // 判断传入的参数n是否是一个质数 // 1及以下的数字,不是质数 if (n <= 1) false else if (n == 2) true else { for (i <- 2 until n) { if (n % i == 0) return false } // 如果整个循环结束,都没有返回false,那么说明所有数字都无法整除 true } } }
案例
// 打印n行*组成的三角形 def printStar(n: Int): Unit = { for (i <- 1 to n) println("*" * i) } // 可以推导出结果类型 def printStar(n: Int) = { for (i <- 1 to n) println("*" * i) } // 如果返回值类型是Unit,那么此时可以省略=不写 def printStar(n: Int) { for (i <- 1 to n) println("*" * i) } // 如果代码只有一行,并且返回值类型还是Unit,那么此时=或者{}只能省略其一 def printStar(n: Int) = for (i <- 1 to n) println("*" * i)
案例
package com.fesco.method object MethodDemo3 { def main(args: Array[String]): Unit = { // 如果函数在调用的时候没有参数,()可以写可以不写 println(rand100()) println(rand100) // 如果函数在定义的时候就没有(),那么调用的时候也不能写() println(rand) } // 产生1-100之间的随机数 def rand100():Int = (Math.random() * 100 + 1).toInt // 函数没有参数的,因此()可以省略 def rand:Int = (Math.random() * 100 + 1).toInt }
参数
可变参数
所谓可变参数,指的是在调用函数的时候,参数个数可以变化
案例
package com.fesco.method object MethodDemo4 { def main(args: Array[String]): Unit = { println(sum(4.2, 1.84, 8.741, 7.2, 2.05)) } // 计算传入的数字的和 // 通过*来定义可变参数 def sum(nums: Double*): Double = { var sum = 0.0 for (n <- nums) sum += n // 将结果返回 sum } }
注意:函数中最多只能定义一个可变参数,并且必须放在参数列表的末尾
默认参数
默认参数,在定义函数的时候,就给参数一个默认值。在调用函数的时候,如果指定了值,就使用指定的来计算;如果没有指定,就使用默认值来计算
案例
package com.fesco.method object MethodDemo5 { def main(args: Array[String]): Unit = { // 在调用函数的时候,如果没有传入折扣,那么就使用默认值 println(offPrice(180)) // 如果传入了折扣,那么就按照传入的折扣来计算 println(offPrice(150, 0.88)) } // 案例:计算打折之后的价格 // 如果需要打折,那么就指定折扣 // 如果不需要打折,希望off就是1 def offPrice(price: Double, off: Double = 1.0): Double = { if (off <= 0 || off > 1) throw new IllegalArgumentException price * off } }
函数的调用
Scala中,也是通过函数名(参数)的形式来调用函数,但是Scala提供了省略调用和带名调用
省略调用:如果函数没有参数,那么在调用函数的时候可以省略()不写
println() // 省略调用 println
如果函数定义的时候有(),那么在调用的时候可以写()也可以不写();如果函数定义的时候就没有(),那么调用的时候就不能有()
带名调用:在调用函数的时候,指定参数名来赋值
package com.fesco.method object MethodDemo6 { def main(args: Array[String]): Unit = { info("bob", "男", 19) info(name = "bob", gender = "男", age = 19) // 带名调用的时候,参数顺序可以不一致 info(gender = "男", age = 19, name = "bob") message(name = "David", age = 15) println(offPrice(180, off = 0.92)) } def info(name: String, gender: String, age: Int) = println(s"姓名:$name\n性别:$gender\n年龄:$age") // 默认参数 def message(name: String, gender: String = "男", age: Int) = println(s"姓名:$name\n性别:$gender\n年龄:$age") def offPrice(price: Double, vip: Int = 0, off: Double = 1) = { if (vip <= 0) price * off else if (vip <= 3) price * 0.95 * off else if (vip <= 7) price * 0.9 * off else price * 0.85 * off } }
函数和方法
Java中,函数就是方法,方法也是函数。在Scala中,函数的范围会比方法要稍微大一点
Scala中,函数可以定义在任何位置,即函数可以定义在类或者函数内
object MethodDemo7 { def main(args: Array[String]): Unit = { // 在函数中定义函数 def sum(x: Int, y: Int) = x + y println(sum(3, 5)) } }
如果需要细分:定义在类中的函数称之为方法,定义在其他地方的就是函数
在Scala中,函数是"一等公民",即函数可以定义在任何地方,也可以被当作参数进行传递,当作结果进行返回,当作变量被赋值
函数赋值给变量/常量
package com.fesco.function object FunctionDemo1 { def main(args: Array[String]): Unit = { // 函数作为"一等公民",可以被定义在任何位置 def rand(): Int = (Math.random() * 100).toInt // 调用函数打印结果 println(rand()) // 将函数的计算结果赋值给变量/常量 val r = rand() println(r) val r2 = rand println(r2) // 不是调用rand函数,而是把rand函数作为数据传递给f // 所以此时可以认为,f既是一个常量,也是一个函数 // 常量f的数据类型是:() => Int val f: () => Int = rand _ println(f) // 调用函数f println(f()) def sum(x: Int, y: Int): Int = x + y // 将sum函数作为一个整体数据,赋值给常量s // 此时可以认为s既是一个常量,也是一个函数 // s的数据是一个函数 // sum的参数类型是(Int, Int),返回值是Int // 所以s的数据类型:(Int, Int) => Int val s: (Int, Int) => Int = sum _ // 打印常量s的数据 println(s) // 调用s中的函数 println(s(3, 5)) // 匿名函数 val a1: (Int, Int) => Int = (x: Int, y: Int) => x + y println(a1) println(a1(2, 4)) // 定义常量a2的时候,已经指定了要封装的参数的类型,在定义函数的时候就可以省略参数类型 val a2: (Int, Int) => Int = (x, y) => x + y println(a2) println(a2(2, 4)) } }
高阶函数
当一个函数的参数是另一个函数,或者返回值是另一个函数的时候,这个函数就是高阶函数
将函数作为参数进行传递
package com.fesco.function object FunctionDemo2 { def main(args: Array[String]): Unit = { // 需求:定义一个函数,对两个整数进行计算,返回一个整数 /* 1. 明确参数 a. 两个整数是未知的,所以需要以参数形式来体现 b. 对这俩整数来进行计算,计算规则是未知的,所以同样,需要将计算规则以参数形式来体现 2. 计算逻辑 要利用传入的规则对两个参数来进行计算 */ def calcInt(x: Int, y: Int, f: (Int, Int) => Int): Int = f(x, y) // 定义计算规则 def times(x: Int, y: Int): Int = x * y // 调用calcInt函数,传入参数和规则 println(calcInt(3, 6, times)) // 新规则 def subtract(x: Int, y: Int): Int = x - y // 传入规则 println(calcInt(3, 6, subtract)) // 可以直接传入规则 println(calcInt(3, 6, (x: Int, y: Int) => { x - y })) // 当函数体只有一句的时候,{}可以省略 println(calcInt(3, 6, (x: Int, y: Int) => x - y)) // 定义calcInt函数的时候,就已经指定了规则f中的参数类型和结果类型,所以参数类型可以省略 println(calcInt(3, 6, (x, y) => x - y)) // 在匿名函数中,依次调用了参数,并且只调用一次,那么可以省略参数列表不写,用_来依次代替每一个参数 println(calcInt(3, 6, _ - _)) println(calcInt(3, 6, _ * _)) } }
将函数作为结果进行返回
package com.fesco.function object FunctionDemo3 { def main(args: Array[String]): Unit = { // 定义一个函数,可以依次传入两个整数求和 // 可以先传入一个整数,然后再传入另一个整数 // 如果只有一个参数,可以省略()不写 // def sum(x: Int): (Int) => Int = { // def sum(x: Int): Int => Int = { // 如果在定义函数的时候,没有写返回值类型,而是由编译器自动推导,那么返回函数的时候,必须添加_ /* def sum(x: Int) = { def add(y: Int): Int = x + y add _ } */ // 如果在定义函数的时候,指定了返回值类型,那么返回函数的时候,可以不用添加_ def sum(x: Int): Int => Int = { def add(y: Int): Int = x + y add } val r = sum(5) // r接收到是add函数 println(r) // 再传入第二参数 val n = r(8) println(n) // 可以第一个参数不变,改变第二个参数 println(r(6)) // 传入两个参数 val n2 = sum(3)(6) println(n2) } }
闭包(closure)
闭包,指的是一个函数,如果访问了外部变量的值,那么此时这个函数以及它所处的函数,构成了闭包
闭包的特点:会延长外部函数中变量的生命周期
package com.fesco.function object ClosureDemo { def main(args: Array[String]): Unit = { // 函数rand中,接收了一个变量n // 正常情况下而言,当rand函数执行结束之后,rand函数所占用的内存应该立即释放 // 因此变量n应该也立即销毁 -> 此时变量n的生命周期随着函数rand的结束而结束 def rand(n: Int): Int = (Math.random() * n).toInt println(rand(10)) println(rand(50)) def random(n: Int) = { def r() = (Math.random() * n).toInt r _ } // 当执行完这句话之后,random函数就应该执行完了 // random函数执行完成之后,如果变量n随着random函数一起被销毁 // 那么会导致产生嵌套的r函数无法执行,所以此时n不会被销毁而是被保留 // 此时n的生命周期不再随着random函数结束而结束 // n的生命周期被延长,这个过程就称之为闭包 val result = random(5) println(result()) } }