跳至主要内容

Scala 中函数与方法的基础

了解方法基础、具名参数、默认参数值、可变参数,以及不同类型的函数。
· 10分钟 阅读

使用 AI 探索

想进一步学习 Scala?请参加 DataCamp 的 Introduction to Scala 课程。

Scala 是一种函数式编程语言,既有作为一等值的函数,也有方法,两者既相似又不同。函数和方法都是可复用的代码块,用于把重复代码集中在一处,并通过函数调用来执行特定任务。它们也让代码更易于调试和修改。

不过,函数是可以初始化到变量中的对象;而方法以关键字 'def' 开始,后接方法名、参数列表、方法体与返回值。

方法的声明与定义

Scala 中方法声明与定义的语法如下:

def 'method_name' ('parameters':'return_type_parameters') : ('return_type_of_method') = {
    'method_body'
     return 'value'
}

Scala 中的方法由以下部分构成:

  1. 'def':用于声明方法的关键字。
  2. 'method_name':方法名,采用 lower camel case(小驼峰)命名。
  3. 'parameters':方法参数,可以没有参数,也可以只有一个参数;当有多个参数时,用逗号分隔。
  4. 'return_type_of_parameters':需要与 'parameters_list' 中的参数数据类型匹配,为必填。
  5. 'return_type_of_method':可选;默认返回 'Unit',也可用 'return' 关键字返回值。
  6. 赋值符号('='):可选;使用时会将返回值赋给方法,不使用则表示方法不返回任何内容。
  7. 'method_body':用花括号 '{}' 包围的代码块,包含所需的逻辑、任务或操作。
  8. return:用于返回所需值并终止程序的关键字,但在 Scala 中较少使用。

方法调用

Scala 中方法调用的语法如下:

    method_name(arguments)

方法调用可以直接使用 'method_name',即要调用的方法名,并传入相应参数。

带具名参数的方法

带具名参数的方法允许在调用时将实参按名称传给方法的参数,每个实参与方法参数一一匹配。下面的示例展示了在函数声明与定义后,如何在方法调用中传递参数:

object calculateResult {
      def funSub(x:Int, y:Int) : Int =
   {

       var diff:Int = 0
       diff = x - y

       // return value
       return diff
   }
   def main(args: Array[String]) {

      // Function call
      println("Difference of the value is: " + funSub(8,6));
      println("Difference of the value is " + funSub(y=6,x=8));
   }
}

上述程序的输出为:
Difference of the value is: 2
Difference of the value is: 2

上面的程序包含一个对象 'calculateResult',其中有一个名为 'funSub' 的方法,参数 x 与 y 的返回类型均为 'Int',方法的总体返回类型也是 'Int'。随后是赋值语句用于返回值赋值。花括号表示方法体的开始,其中变量 'diff' 被初始化为 0。在主方法中调用 'funSub(8,6)',将 8 匹配给 'x'、6 匹配给 'y',执行减法操作,返回 'diff' 的值并打印。类似地,'funSub(x=8,y=6)' 在方法调用时按名称将 6 匹配给 'y'、8 匹配给 'x',参数顺序无关,执行相同操作并返回结果并打印。

默认参数值

您可以通过为方法参数设置默认值来初始化相应的数值,并在调用方法时省略参数。

object calculateResult {
      def funSub(x:Int=9, y:Int=6) : Int =
   {

       var diff:Int = 0
       diff = x - y

       // return value
       return diff
   }
   def main(args: Array[String]) {

      // Function call
    print( "The final value is: " + funSub() );

   }
}

上述程序的输出为:
The final value is: 3

如上所示,该程序包含一个名为 'calculateResult' 的对象,内部有方法 'funSub',参数 x 和 y 的返回类型均为 'Int',方法的总体返回类型也是 'Int',随后有赋值语句用于返回值赋值。花括号表示方法体的开始,其中变量 'diff' 被初始化为 0。主方法中调用 'funSub()',将 x 初始化为 9、y 初始化为 6,执行运算后返回并打印 'diff' 的值。

可变长度参数

可变长度参数可以接收任意数量的实参,由用户或客户端传入。方法中的最后一个参数使用 '*' 声明,表示该参数可重复。

object variableArgument {
    def main(args: Array[String]) {
       printAll("Scala", "is", "great")
    }

    def printAll(strings: String*) {
        var i : Int = 0;

      for( value <- strings ){
         println(value);
         i = i + 1;
      }
    }
}

上述程序的输出为:
Scala
is
great

可以看到,该程序包含对象 'variableArgument' 和方法 'printAll',其中在末尾定义了可变长度参数 'String*'。调用方法时可以传入一个字符串列表。传入的字符串列表会被遍历,并在主函数中逐个输出。

递归函数

递归是函数式编程中常用的一种技术,通过将问题不断分解为与原问题相似但更小的子问题来求解。先解决更小的问题,再将其解进行组合,得到原问题的解。因此,这是一种分治或减治的思路,使代码更简洁优雅。编写递归函数时需要严格考虑两点:

  • 基本情况(Base case):函数可能有一个或多个基本情况,必须找出它们,否则可能导致无限递归。基本情况需要有直接解,即无需递归即可得到的简单输出或解。
  • 递归情况(Recursive case):应用递归函数的情形,即进行递归调用。关键在于每次递归调用都要缩小问题规模,将问题不断分解为更小的子问题,直到到达基本情况。最后将各子解组合得到主问题的解,这就是分治方法。

任意给定数的阶乘为 n!,可用数学形式表示如下:

n! = n.(n-1).(n-2)...3.2.1

同时,可将 (n-1).(n-2)...3.2.1 记为 (n-1)!,因此任意给定数的阶乘总是 n.(n-1)!

下面展示递归求解该问题的方法。

object factorialDemo {
   def main(args: Array[String]) {
      for (i <- 1 to 5)
         println( "Factorial of " + i + ": = " + factorial(i) )
   }

   def factorial(n: Int): Int = {  
      if (n <= 1)
         return 1  
      else    
      return n * factorial(n - 1)
   }
}

上述程序的输出为:
Factorial of 1: = 1
Factorial of 2: = 2
Factorial of 3: = 6
Factorial of 4: = 24
Factorial of 5: = 120

如上所示,对象名为 'factorialDemo',其中的 'main' 函数包含一个循环,重复五次,并在每次迭代中调用 'factorial'。该递归函数进行递归调用,直到 n 小于或等于 1 的基本情况时停止,然后返回结果并打印。

匿名函数

匿名函数是指没有名称的轻量级函数定义,在 Scala 中也称为函数文本(function literal)。

匿名函数的语法与示例如下:

  • 第一种语法及示例:

语法:

('first_variable':'data_type', 'second_variable':'data_type') => "certain_expression"

示例:

(var1:String:var2:String) => var1 + var2

或

  • 第二种语法及示例:

语法:

 (_:'data_type')operator(_'data_type')

示例:

 (_:String)+(_:String)

第一种语法表示 "=>" 之后的表达式会被求值为某个特定值,而 "=>" 之前的变量列表用于对该表达式进行求值。

第二种语法类似占位符方式,只接受一次作为“通配”字符的值,然后对其应用运算符。

下面给出一个匿名函数的示例:

 object anonymousDemo  
{
    def main(args: Array[String])  
    {


        var function1 = (var1:Int, var2:Int) => var1 + var2
        var function2 = (_:Int) + (_:Int)

        // function call
        println(function1(5, 5))
        println(function2(7, 3))
    }
}
Initializing Scala interpreter ...



Spark Web UI available at http://DESKTOP-03TH7J0:4040
SparkContext available as 'sc' (version = 2.4.3, master = local[*], app id = local-1566986265958)
SparkSession available as 'spark'




<console>:10: error: unbound placeholder parameter

        var function2 = {_:Int} + {_:Int}

                         ^

<console>:10: error: unbound placeholder parameter

        var function2 = {_:Int} + {_:Int}

                                   ^

上述程序的输出为:
10
10

如上所示,对象名为 'anonymousDemo',其 'main' 函数包含两个匿名函数。二者语法不同,但结果相同:'function1' 在被调用时从函数调用中获取参数——传入 5 与 5,输出为 10;'function2' 同样在调用时传入 7 与 3,它以占位符方式各接受一次任意有效的 'Integer',执行加法并输出结果。

结论

恭喜,您已完成本教程的阅读。

Scala 中还有许多进阶主题,例如高阶函数、嵌套函数、柯里化等。

参考资料:
TOUR OF SCALA BASICS
Scala | Functions – Basics
Scala - Functions
Anonymous Functions in Scala

欢迎查看 DataCamp 的 Introduction to Scala 课程。

主题