[Scala]003-函数/方法

函数,目的是为了重用性、可组合性、简短、可读。

在Scala中,函数(function)、方法(method)在语义上的差别很小。但不同的“语境“下:

  • 函数,是一个完整的对象,可以赋值给一个变量,并且可将其作为一个参数传入到方法中(而方法不可以)。不过它其实是继承了Trait(相当于Java的接口,而实际上比接口 功能还更强大)的类的对象
  • 方法,是组成的一部分,即在类中定义的函数就是方法;
  • 使用val关键字可以定义函数,def关键字可以定义方法

在这里插入图片描述
Scala在省略代码量是下足功夫,只要是能推断出来的,都可以不写!!甚至连def{}、函数名、返回值类型、=等都可以省略!!!

以下将围绕函数组成部分中的 参数、返回值的数据类型、函数体:

一、关于参数

1、纯函数、非纯函数
纯函数:输入输出数据流全是显式的(Explicit),就是说 函数体内的参数全部由函数提供,非纯函数反之。

scala> def add(a:Int,b:Int) = a + b //纯函数。在实际中,用的比较少
add: (a: Int, b: Int)Int

scala> add(1,2)
res0: Int = 3

scala> var a = 1
a: Int = 1

scala> def addA(b:Int) = a + b //非纯函数。实际中用的比较多,但是开发过程尽量少用。不过也避免不了,好多情况是纯函数解决不了的
addA: (b: Int)Int

scala> addA(2)
res1: Int = 3

2、括号内是参数列表,各个参数之间用逗号隔开。若有参数,那么所有参数就必须指定数据类型(就算是默认参数也必须指定);没有参数,可以不写括号()

scala> def my_max(x:Int, y:Int) = if (x>y) x else y
my_max: (x: Int, y: Int)Int

scala> my_max(1,2)
res15: Int = 2

scala> def a = 1
a: Int

scala> a
res16: Int = 1

scala> def hi = "hi"
hi: String

scala> hi() //调用时,不能使用hi()
<console>:9: error: not enough arguments for method apply: (index: Int)Char in class StringOps.
Unspecified value parameter index.
              hi()
                ^

scala> hi
res3: String = hi

scala> def hi() = "hi" //调用时,可用hi、hi()都行
hi: ()String

scala> hi
res4: String = hi

scala> hi()
res5: String = hi

3、默认参数:有默认值的参数
在函数定义时,直接为该参数 赋好值。

  • 在调用它时,可以不设置已有默认值的参数;
  • 也可以在调用时,设置默认参数的值,设置值时会按照函数该默认参数所在的顺序依次设置。传统调用函数时,参数都是按照顺序。
  • 也可以在调用时,指定参数名对其进行赋值。新值会覆盖掉默认值。此时,如下的参数left="#"就称之为命名参数,这样就不用按照顺序了。
scala> def m2(name:String, left:String="<", right:String=">") = {
     |     left + name + right
     | }
m2: (name: String, left: String, right: String)String

scala> m2("mayun")
res10: String = <mayun>

scala> m2("mayun", "<<")
res11: String = <<mayun>

scala> m2("mayun",left="#")
res12: String = #mayun>

这在Spark或其他时,可用于设置默认配置等等。很有用处。

4、变长参数(可变参数):不确定参数个数时使用

scala> def sum(args:Int*) = {
     |     var res = 0
     |     for(arg <- args) res += arg
     |     res
     | }
sum: (args: Int*)Int

scala> sum(1,2,3,4,5,6,7,8,9)
res14: Int = 45

scala> sum(1 to 9)
<console>:9: error: type mismatch;
 found   : scala.collection.immutable.Range.Inclusive
 required: Int
              sum(1 to 9)
                    ^

scala> sum(1 to 9 :_*)
res16: Int = 45

上述sum函数的参数类型是Seq类型,如果有一个值的序列不能传入到变长参数中,此时会报错:type mismatch类型不能匹配

Scala设计的解决方法是:在传入的已有序列后追加 :_*,将一个Range转成一个Seq

5、参数组
将参数分解成参数组。

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

等价

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

scala> add(1,2) //但调用时,这样是错误的!
<console>:9: error: too many arguments for method add: (x: Int)(y: Int)Int
              add(1,2)
                 ^

scala> add(1)(2)
res19: Int = 3

6、传名参数
表现形式:(参数名: =>Int)。注意英文冒号:=>之间有空格

scala> def doub(x: =>Int) = x*2
doub: (x: => Int)Int

scala> doub(2)
res31: Int = 4

scala> doub(doub(2))
res32: Int = 8

这样的函数,它的参数可以接受、也可以接受函数(当然所接受函数的返回值得是一致的,如上述的Int

7、类型参数
前面传的都是值 参数,现在向函数传递类型参数

//需求:函数输入什么数据类型,函数就返回什么类型。
//方案:给函数传入个类型参数,根据传入的类型,返回什么类型。
scala> def m3[A](a:A):A=a
m3: [A](a: A)A

scala> m3("hi")
res23: String = hi

scala> m3(1.0)
res24: Double = 1.0

scala> m3(1)
res25: Int = 1

scala> m3(true)
res26: Boolean = true

8、命名参数

函数调用时,传参时,把参数名带上,就与顺序无关了。有这种 通过名字识别的机制。
当然,最好是按常理出牌,按顺序。

scala> def speed(distance:Float, time:Float):Float = {
     |    distance/time
     | }
speed: (distance: Float, time: Float)Float

scala> speed(100,10)
res32: Float = 10.0

scala> speed(distance=100,time=10)
res33: Float = 10.0

scala> speed(time=10,distance=100)
res34: Float = 10.0

二、关于返回值

1、返回值的数据类型 写不写?
递归函数不可省略返回值的数据类型,其他情况可以省略(函数可以通过等号右侧的函数体推断出返回值的数据类型)

2、返回值也可以不写,写上将会有更好的可读性。默认函数体最后一个表达式(最后一行) 为返回值,不需要使用return。但是需要在函数的表达式快结束前退出 并返回其值,可使用关键字return

scala> def m1(str:String):String = {
     |     if(str == null) return null
     |     str.trim()
     | }
m1: (str: String)String

scala> m1(null)
res7: String = null

scala> m1("hello ")
res8: String = hello

Scala函数都有返回值,只不过有的返回的是 Unit,()

scala> def greet() = println("Hello, world!")
greet: ()Unit

scala> greet
Hello, world!

scala> def greet = println("Hello, world!")
greet: Unit

scala> greet
Hello, world!

其中,Unit是Scala语言中 数据类型的一种,表示无值,用于不返回任何结果的函数/方法。(类似Java中的Void)

3、过程
在定义函数时,如果函数体直接包裹在了{}中,而没有用=连接,那么函数的返回值类型就是Unit(即便在函数定义时指定了返回值,返回值类型依旧是Unit)。这样的函数称之为 过程

scala> def greet{println("Hello,world!")}
greet: Unit

scala> greet
Hello,world!

综合:函数类型和函数值
函数类型,由输入类型、输出类型的一个简单组合。有一个箭头从输入类型指向输出类型。

scala> def doub(x:Int) = x*2 //此处的函数类型为 Int=>Int
doub: (x: Int)Int

scala> val myDoub:Int=>Int=doub //Int=>Int 是一个整体,代表函数类型
myDoub: Int => Int = <function1>

scala> myDoub(3)
res29: Int = 6

myDoub,是函数值。

scala> val md = doub _
md: Int => Int = <function1>

scala> md(3)
res30: Int = 6

上述没有显示函数类型(Int=>Int)来区分函数调用。下划线 _相当于一个占位符,表示一个函数调用,在此会返回一个函数值,并将这个值保存到md变量中。

三、关于函数体

1、只有一条语句时,花括号{}可以省略。单行写

2、函数体中,可以加入各种控制结构。

3、函数体是最重要的部分。但除此之外的部分却又很多花样!!!

四、彩蛋?匿名函数

函数字面值,业界喜欢称之为 匿名函数。语法:

关键字 变量名 =  (参数名:数据类型) => {函数体}
scala> var f = (x:Int) => x+1
f: Int => Int = <function1>

scala> f(2)
res47: Int = 3

scala> var mul = (x:Int,y:Int) => x*y
mul: (Int, Int) => Int = <function2>

scala> mul(2,3)
res48: Int = 6

正常函数是 参数、函数体之间用 = 连接;
匿名函数是 参数、函数体之间用 => 连接。

其他各种“特色”函数:递归函数、嵌套函数、高阶函数、偏函数等,以后细讲

五、还是彩蛋?函数、方法的区别

理论方面在开头已讲述。
1、定义方式
方法(method)一般是以def关键字开头,带有参数列表(或无)的一个逻辑操作块。这其实就像objectclass中的成员方法一样。

函数(function)是一个赋值给一个变量的匿名方法(带、或不带参数列表),并且可以通过=>转换符号连接逻辑代码块的一个表达式。=>转换符号后面的逻辑代码块写法 跟方法(上述将的函数体)相同。

scala> def m1(x:Int) = x+1 //定义方法
m1: (x: Int)Int

scala> val f1 = (x:Int) => x+1 //定义函数
f1: Int => Int = <function1>

2、表现形式

scala> m1
<console>:9: error: missing arguments for method m1;
follow this method with `_' if you want to treat it as a partially applied function
              m1
              ^

scala> f1
res34: Int => Int = <function1>

方法(带参)不能作为最终的表达式,如上报错了;
不带参数的方法可以,但此时就是方法调用了(本质上不同了),因为Scala允许无参方法调用时省略括号()

函数可以作为最终的表达式出现。

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

3、参数要求

scala> def m2 = 666 //不带参数列表。即没有入参的函数,调用时不用写括号。
m2: Int

scala> m2
res38: Int = 666

scala> m2() //本身就没有参数。写法本身就错了
<console>:9: error: Int does not take parameters
              m2()
                ^

scala> def m3()=555 //空的参数列表
m3: ()Int

scala> m3
res40: Int = 555

scala> m3()
res41: Int = 555

scala> val f2=() => 666 //必须设置参数列表,否则报错
f2: () => Int = <function0>

scala> val f2= => 666
<console>:1: error: illegal start of simple expression
       val f2= => 666
               ^

方法 可以没有参数列表;也可以是空的参数列表。
函数 必须设置参数列表,可以是空的参数列表,但一定要设置。

4、函数、方法相互转换
x => func(x) 简化为 func _func的过程称之为:ETA-conversion(函数转换成方法);
func _func 展开为 x => func(x)的过程称之为:ETA-expansion(方法转换成函数)。

scala> def m4(x:Int) = f1(x) //把函数转换成方法
m4: (x: Int)Int

scala> m4(2)
res44: Int = 3

scala> val f3 = m1 _ //把方法转换成函数
f3: Int => Int = <function1>

scala> f3(2)
res43: Int = 3

其中,下划线_是作为Scala中的占位符,它可以用传入的参数代替。这就是占位符语法

scala> val f5 = (_:Int) + (_:Int)
f5: (Int, Int) => Int = <function2>

scala> f5(2,3)
res46: Int = 5

占位符 还可以用于设置函数的别名,如上述m1,在此m1不用传入参数,直接加个占位符即可。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值