scala中函数和方法

Posted YaoYong_BigData

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了scala中函数和方法相关的知识,希望对你有一定的参考价值。

一、scala中的函数

Scala中,函数上升和变量同等的位置,或者说函数也是一种变量;

Scala中的函数可以作为实参传递给另一个函数;

函数可以作为返回值;

函数可以赋值给变量(这个变量需符合函数的类型的变量);

函数可以存储在数据结构之中;

函数如同普通变量一样,也具有类型。

函数的定义可以有很多种,因此需要掌握最基本的定义方法:

1.函数的基本定义

语法:

1.1 完整语法

val 函数名 :(参数类型)=>返回值类型 = (参数名称:参数类型)=>函数体

val add1:(Int,Int)=>Int =(a:Int,b:Int)=>a +b

1.2 简写语法

val 函数名 = (参数名称:参数类型)=>函数体

val add2 =(a:Int,b:Int)=>a +b
  • 函数是一个对象(变量)

  • 类似于方法,函数也有输入参数和返回值

  • 函数定义不需要使用def定义

  • 无需指定返回值类型

val f1 = (a: Int, b: Int) => a + b
val f2 = ((a: Int, b: Int) => a + b)
val f3 = (_: Int) + (_: Int)
val f7 = ()
val f4: (Int, Int) => Int = (_ + _)
val f5:(Int,Int)=>Int =(x,y)=>x+y
val f6:(Int,Int)=>Int=(m,n)=>if (m+n>0) m else n

2.自定义函数

scala> val f1 = new Function2[Int, Int, Int] 
     |    def apply(x: Int, y: Int): Int = if (x < y) y else x
     | 
f1: (Int, Int) => Int = <function2>

3.匿名函数

     没有名称的函数是匿名函数,也称为函数文字,或者函数字面量。

定义格式:
(形参列表) => 函数体
例如: (x: Int, y: Int) => x + y
res0: (Int, Int) => Int = <function2>

二、scala中的方法

        一个类可以有自己的方法,scala中的方法和Java方法类似。但scala与Java定义方法的语法是不一样的。

1.定义方法

语法:

def methodName (参数名:参数类型, 参数名:参数类型) : [return type] = 
    // 方法体:一系列的代码
  • 参数列表的参数类型不能省略;

  • 返回值类型可以省略,由scala编译器自动推断;

  • 返回值可以不写return,默认就是块表达式的值。

“=”并不只是用来分割函数签名和函数体的,它的另一个作用是告诉编译器是否对函数的返回值进行类型推断!如果省去=,则认为函数是没有返回值的!

例如:

##缺少等号时,表名该方法是没有返回值的
scala> def myFirstMethod() "exciting times ahead" 

<console>:11: warning: a pure expression does nothing in statement position; you may be omitting necessary parentheses
       def myFirstMethod() "exciting times ahead" 
myFirstMethod: ()Unit

scala> def myFirstMethod()= "exciting times ahead" 
myFirstMethod: ()String

2.返回值类型推断

scala定义方法可以省略返回值,由scala自动推断返回值类型。这样方法定义后更加简洁。 

定义递归方法,不能省略返回值类型。

示例
定义递归方法(求阶乘)
10 * 9 * 8 * 7 * 6 * ... * 1

参考代码:

scala> def m2(x:Int) = 
     | if(x<=1) 1
     | else m2(x-1) * x
     | 
<console>:13: error: recursive method m2 needs result type
       else m2(x-1) * x

应该定义为:
def m2(x:Int):Int = if(x<=1) 1  else m2(x-1) * x 

3.方法参数

scala中的方法参数,使用比较灵活。它支持以下几种类型的参数:

  • 默认参数

  • 带名参数

  • 变长参数

3.1 默认参数

在定义方法时可以给参数定义一个默认值。

示例
1. 定义一个计算两个值相加的方法,这两个值默认为0
2. 调用该方法,不传任何参数
参考代码:

// x,y带有默认值为0 
scala> def add(x:Int = 0, y:Int = 0) = x + y
add: (x: Int, y: Int)Int

scala> add()
res1: Int = 0

3.2 带名参数

在调用方法时,可以指定参数的名称来进行调用。
示例
1. 定义一个计算两个值相加的方法,这两个值默认为0
2. 调用该方法,只设置第一个参数的值

参考代码:

scala> def add(x:Int = 0, y:Int = 0) = x + y
add: (x: Int, y: Int)Int

scala> add(x=1)
res2: Int = 1

3.3 变长参数

如果方法的参数是不固定的,可以定义一个方法的参数是变长参数。
语法格式:

def 方法名(参数名:参数类型*):返回值类型 = 
    方法体

在参数类型后面加一个*号,表示参数可以是0个或者多个 。

示例
1. 定义一个计算若干个值相加的方法
2. 调用方法,传入以下数据:1,2,3,4,5
参考代码:

scala> def add(num:Int*) = num.sum
add: (num: Int*)Int

scala> add(1,2,3,4,5)
res1: Int = 15

4.方法调用方式

在scala中,有以下几种方法调用方式,

  • 后缀调用法

  • 中缀调用法

  • 花括号调用法

  • 无括号调用法

4.1 后缀调用法

这种方法与Java没有区别。
语法:

对象名.方法名(参数)

示例
使用后缀法Math.abs求绝对值
参考代码:

    scala> Math.abs(-1)
    res3: Int = 1

4.2 中缀调用法

语法

 对象名 方法名 参数

例如:1 to 10

如果有多个参数,使用括号括起来。

示例

使用中缀法Math.abs求绝对值

    scala> Math abs -1
    res4: Int = 1

4.3 操作符即方法

来看一个表达式:

 1 + 1

大家觉得上面的表达式像不像方法调用?

在scala中,+ - * / %等这些操作符和Java一样,但在scala中

  • 所有的操作符都是方法;

  • 操作符是一个方法名字,是符号的方法。

4.4 花括号调用法

语法:

Math.abs 
    // 表达式1
    // 表达式2

方法只有一个参数,才能使用花括号调用法 。

示例

使用花括号调用法Math.abs求绝对值

参考代码:

    scala> Math.abs-10
    res13: Int = 10

4.5 无括号调用法

如果方法没有参数,可以省略方法名后面的括号。

示例

  • 定义一个无参数的方法,打印"hello"

  • 使用无括号调用法调用该方法

scala> def m3()=println("hello")
m3: ()Unit

scala> m3
hello

三、scala中的函数和方法区别

  • 方法是隶属于类或者对象的,在运行时,它是加载到JVM的方法区中;

  • 可以将函数对象赋值给一个变量,在运行时,它是加载到JVM的堆内存中;

  • 函数是一个对象,继承自FunctionN,函数对象有apply,curried,toString,tupled这些方法。方法则没有;

  • 函数声明在方法体中,方法声明在类中方法外;

1.区别一

有参方法可以作为表达式的一部分出现(调用函数并传参),但带参方法不能作为最终的表达式出现;但是函数可以作为最终的表达式出现。

scala> //定义一个方法
 
scala> def m(x:Int) = 2*x
m: (x: Int)Int
 
scala> //定义一个函数
 
scala> val f = (x:Int) => 2*x
f: Int => Int = <function1>
 
scala> //方法不能作为最终表达式出现
 
scala> m
<console>:9: error: missing arguments for method m;
follow this method with `_‘ if you want to treat it as a partially applied function
              m
              ^
 
scala> //函数可以作为最终表达式出现
 
scala> f
res9: Int => Int = <function1>

无参方法可以作为最终表达式出现,其实这属于方法调用,scala规定无参方法的调用可以省略括号,所以这并不违法上面的说法;

scala> def m1()=1+2
m1: ()Int
 
scala> m1
res10: Int = 3

2.区别二

参数列表对于方法是可选的,但是对于函数参数列表是强制的。

scala> //方法可以没有参数列表
 
scala> def m2 = 100;
m2: Int
 
scala> //方法可以有一个空的参数列表
 
scala> def m3() = 100
m3: ()Int
 
scala> //函数必须有参数列表,否则报错
 
scala> var f1 =  => 100
<console>:1: error: illegal start of simple expression
       var f1 =  => 100
                 ^
 
scala> //函数也可以有一个空的参数列表
 
scala> var f2 = () => 100
f2: () => Int = <function0>

即参数列表对于函数来说是必须的,哪怕这是一个空的参数列表。

3.区别三

方法名意味着方法调用;但函数名只是代表函数自身。

        因为方法不能作为最终的表达式存在,所以如果你写了一个方法的名字并且该方法不带参数(没有参数列表或者无参) 该表达式的意思是:调用该方法得到最终的表达式。

        因为函数可以作为最终表达式出现,如果你写下函数的名字,函数调用并不会发生,该方法自身将作为最终的表达式进行返回,如果要强制调用一个函数,你必须在函数名后面写()。

scala> def m1()=1+2       //定义一个空参方法
m1: ()Int
 
scala> def m2 = 100;    // 定义个无参方法
m2: Int
 
scala> var f2 = () => 100  // 定义一个空参函数
f2: () => Int = $$Lambda$1047/510873326@1e191150
 
scala> m1               //调用一个空参方法
res3: Int = 3
 
scala> m2			   //调用一个无参方法
res4: Int = 100
 
scala> f2      //得到函数自身,不会发生函数调用
res5: () => Int = $$Lambda$1047/510873326@1e191150
 
scala> f2()    // 需要使用函数名()的形式才会发生函数调用
res6: Int = 100

4.区别四

        在期望出现函数的地方使用方法,该方法自动转换成函数;手动强制转换可以使用 "方法名  _" 转换成函数。

        在scala中很多高级函数,如map(),filter()等,都是要求提供一个函数作为参数。但是为什么我们可以提供一个方法呢?就像下面这样:

scala> val myList = List(3,56,1,4,72)
myList: List[Int] = List(3, 56, 1, 4, 72)
 
scala> // map()参数是一个函数
 
scala> myList.map((x) => 2*x)
res15: List[Int] = List(6, 112, 2, 8, 144)
 
scala> //尝试给map()函提供一个方法作为参数
 
scala> def m4(x:Int) = 3*x
m4: (x: Int)Int
 
scala> //正常执行
 
scala> myList.map(m4)
res17: List[Int] = List(9, 168, 3, 12, 216)

        这是因为,如果期望出现函数的地方我们提供了一个方法的话,该方法就会自动被转换成函数。该行为被称为ETA expansion。

这样的话使用函数将会变得简单很多。你可以按照下面的代码验证该行为:

scala> //期望出现函数的地方,我们可以使用方法

scala>  val f3:(Int)=>Int = m4
f3: Int => Int = <function1>

scala> //不期望出现函数的地方,方法并不会自动转换成函数

scala> val v3 = m4
<console>:8: error: missing arguments for method m4;
follow this method with `_‘ if you want to treat it as a partially applied function
       val v3 = m4

利用这种自动转换,我们可以写出很简洁的代码,如下面这样:

scala> val list = List(1,2,10,34,56)    // 定义一个list集合
list: List[Int] = List(1, 2, 10, 34, 56)
//10.< 被解释成obj.method,即整形的<的方法,所以该表达式是一个方法,会被解释成函数
scala> list.filter(10.<)   
res9: List[Int] = List(34, 56)

因为在scala中操作符被解释称方法:

前缀操作符:op obj 被解释称obj.op
中缀操作符:obj1 op obj2被解释称obj1.op(obj2)
后缀操作符:obj op被解释称obj.op
你可以写成10<而不是10.< 所以上面有多种写法:

list.filter(10<) 或者 list.filter(10.<) 或者 list.filter(10.<_)

四、方法转换为函数

  • 有时候需要将方法转换为函数,作为变量传递,就需要将方法转换为函数

  • 使用_即可将方法转换为函数

示例

  1. 定义一个方法用来进行两个数相加

  2. 将该方法转换为一个函数,赋值给变量

参考代码

scala> def add(x:Int,y:Int)=x+y
add: (x: Int, y: Int)Int

scala> val a = add _
a: (Int, Int) => Int = <function2>

注意:val a = add _ : add与_之间有空格。

五、补充:函数的return

即时函数的return是返回到调用这个函数的块外部,而不是返回到函数返回点。

如果我们直接在一个继承于App的类中定义:

scala> val f = (x: Int) =>  return x; 2 
<console>:11: error: return outside method definition
       val f = (x: Int) =>  return x; 2 

会提示我们:return outside method definition,现在我们把它放在一个方法中:

  def outter: Int = 
    val f = (x: Int) =>  return x; 2 
    println("before.")
    f(1)
    println("after.")
    3
  

  println(outter)

输出:

before.
1
也就是f(1)后面的任何代码都没有执行,到了f(1)中的return 已经返回到它外部的outter而不是返回(x:Int)的返回点。

以上是关于scala中函数和方法的主要内容,如果未能解决你的问题,请参考以下文章

何时在 Scala 中缀表示法中使用括号

Scala - 定义自己的中缀运算符

在 Scala 中将中缀转换为后缀表示法

Scala中中缀运算符的实际优先级

28.scala的运算符

2021年大数据常用语言Scala:基础语法学习 方法调用方式