Scala函数及抽象化
函数字面量及函数的定义
Scala中函数为头等公民,不仅可以定义一个函数然后调用它,还可以写一个未命名的函数字面量,然后可以把它当成一个值传递到其它函数或是赋值给其它变量。
函数字面量体现了函数式编程的核心理念。字面量包括整数字面量、浮点数字面量、布尔型字面量、字符字面量、字符串字面量、符号字面量、函数字面量等。什么是函数字面量呢?
在函数式编程中,函数是“头等公民”,可以像任何其他数据类型一样被传递和操作。函数的使用方式和其他数据类型的使用方式完全一致,可以像定义变量那样去定义一个函数,函数也会和其他变量一样,有类型有值;
就像变量的“类型”和“值”是分开的两个概念一样,函数的“类型”和“值”也成为两个分开的概念;
函数的“值”,就是“函数字面量”。
1 | scala> def add1(x: Int): Int = { x + 1 } |
函数类型:(输入参数类型列表) => (输出参数类型列表)
只有一个参数时,小括号可省略;函数体中只有1行语句时,大括号可以省略;
把函数定义中的类型声明部分去除,剩下的就是函数的“值”,即函数字面量:
对 add1 而言函数的值为:(x) => x+1
对 add2 而言函数的值为:(x, y) => x+y
对 add3 而言函数的值为:(x, y, z) => x+y+z
对 add4 而言函数的值为:(x, y, z) => (x+y, y+z)
在Scala中我们这样定义变量: val 变量名: 类型 = 值 ;
我们可以用完全相同的方式定义函数: val 函数名: 函数类型 = 函数字面量
1 | val add1: Int => Int = (x) => x+1 |
在Scala中有自动类型推断,所以可以省略变量的类型 val 变量名 = 值 。
同样函数也可以这样: val 函数名 = 函数字面量
1 | val add1 = (x: Int) => x + 1 |
备注:要让编译器进行自动类型推断,要告诉编译器足够的信息,所以添加了 x 的类型信息。
函数的定义:
1 | val 函数名: (参数类型1,参数类型2) => (返回类型) = 函数字面量 |
函数与方法的区别
1 | scala> def addm(x: Int, y: Int): Int = x + y |
严格的说:使用 val 定义的是函数(function),使用 def 定义的是方法(method)。二者在语义上的区别很小,在绝大多数情况下都可以不去理会它们之间的区别,但是有时候有必要了解它们之间的不同。
Scala中的方法与函数有以下区别:
-
Scala 中的方法与 Java 的类似,方法是组成类的一部分
-
Scala 中的函数则是一个完整的对象。Scala 中用 22 个特质(从 Function1 到 Function22)抽象出了函数的概念
-
Scala 中用 val 语句定义函数,def 语句定义方法
1 | // 下面用三种方式定义了函数,其中第二种方式最常见 |
-
方法不能作为单独的表达式而存在,而函数可以;
1
2
3
4
5
6
7
8
9
10
11
12// 方法不能作为单独的表达式而存在,而函数可以
scala> def addm(x: Int, y: Int): Int = x + y
addm: (x: Int, y: Int)Int
scala> val addf = (x: Int, y: Int) => x + y
addf: (Int, Int) => Int = <function2>
scala> addm
<console>:13: error: missing argument list for method addm
scala> addf
res8: (Int, Int) => Int = <function2> -
函数必须要有参数列表,而方法可以没有参数列表;
1
2
3
4
5
6
7// 函数必须要有参数列表,而方法可以没有参数列表
scala> def m1 = "This is lagou edu"
m1: String
// 函数必须有参数列表
scala> val f1 = () => "This is lagou edu"
f1: () => String = <function0> -
方法名是方法调用,而函数名只是代表函数对象本身;
1
2
3
4
5
6
7
8
9
10
11// 方法名是方法调用
scala> m1
res16: String = This is lagou edu
// 函数名代表函数对象
scala> f1
res17: () => String = <function0>
// 这才代表函数调用
scala> f1()
res18: String = This is lagou edu -
在需要函数的地方,如果传递一个方法,会自动把方法转换为函数
1
2
3
4
5
6
7
8
9// 需要函数的地方,可以传递一个方法
scala> val list = (1 to 10).toList
lst: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> def double(x: Int) = x*x
double: (x: Int)Int
scala> list.map(double(_))
res20: List[Int] = List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100)
将方法转换为函数:
1 | scala> def f1 = double _ //注意:方法名与下划线之间有一个空格 |
写程序的时候是定义方法、还是定义函数?
一般情况下,不对二者做区分,认为都是函数,更多的时候使用def定义函数。
匿名函数与占位符
函数没有名字就是匿名函数;
匿名函数,又被称为 Lambda 表达式。 Lambda表达式的形式如下:
(参数名1: 类型1, 参数名2: 类型2, … …) => 函数体
1 | // 定义匿名函数 |
多个下划线指代多个参数,而不是单个参数的重复运用
-
第一个下划线代表第一个参数
-
第二个下划线代表第二个参数
-
第三个……,如此类推
高阶函数
高阶函数:接收一个或多个函数作为输入 或 输出一个函数。
函数的参数可以是变量,而函数又可以赋值给变量,由于函数和变量地位一样,所以函数参数也可以是函数;
常用的高阶函数:map、reduce、flatMap、foreach、filter、count … … (接收函数作为参数)
1 | package lagou.cn.part08 |
闭包
闭包是一种函数,一种比较特殊的函数,它和普通的函数有很大区别:
1 | // 普通的函数 |
闭包是在其上下文中引用了自由变量的函数;
闭包引用到函数外面定义的变量,定义这个函数的过程就是将这个自由变量捕获而构成的一个封闭的函数,也可理解为”把函数外部的一个自由变量关闭进来“。
何为闭包?需满足下面三个条件:
1、闭包是一个函数
2、函数必须要有返回值
3、返回值依赖声明在函数外部的一个或多个变量,用Java的话说,就是返回值和定义的全局变量有关
柯里化
函数编程中,接收多个参数的函数都可以转化为接收单个参数的函数,这个转化过程就叫柯里化(Currying)
Scala中,柯里化函数的定义形式和普通函数类似,区别在于柯里化函数拥有多组参数列表,每组参数用小括号括起来。
Scala API中很多函数都是柯里化的形式。
1 | // 使用普通的方式 |
部分应用函数
部分应用函数(Partial Applied Function)也叫偏应用函数,与偏函数从名称上看非常接近,但二者之间却有天壤之别。
部分应用函数是指缺少部分(甚至全部)参数的函数。
如果一个函数有n个参数, 而为其提供少于n个参数, 那就得到了一个部分应用函数。
1 | // 定义一个函数 |
偏函数
偏函数(Partial Function)之所以“偏”,原因在于它们并不处理所有可能的输入,而只处理那些能与至少一个 case 语句匹配的输入;
在偏函数中只能使用 case 语句,整个函数必须用大括号包围。这与普通的函数字面量不同,普通的函数字面量可以使用大括号,也可以用小括号;
被包裹在大括号中的一组case语句是一个偏函数,是一个并非对所有输入值都有定义的函数;
Scala中的Partial Function是一个trait,其类型为PartialFunction[A,B],表示:接收一个类型为A的参数,返回一个类型为B的结果。
1 | // 1、2、3有对应的输出值,其它输入打印 Other |
需求:过滤List中的String类型的元素,并将Int类型的元素加1。
1 | package lagou.cn.part08 |