1、模式匹配
(1)match、case
// 演示 - 模式匹配
def dec(no : Int): Unit = {
println("#" * 20 + s" e.g. #${no} " + "#" * 20)
}
// #1 模式匹配小例子
dec(1)
var metric = 0
val mood = ":)"
mood match {
case ":`(" => println(s"Tearing, I'm sad ${mood}. ") ; metric -= 1
case ":)" => println(s"Smiling, I'm happy ${mood}"); metric += 1
case _ => println("Boring, nothing special. "); metric += 0
}
metric
// #2 Case守卫,匹配某种类型的所有值
dec(2)
val inputData = '3'
var digit = -1
inputData match {
case 'a' => "a"
case 'b' => "b"
case 6 => "6"
case _ if Character.isDigit(inputData) => digit = Character.digit(inputData, 10)
}
s"e.g. #2 Result: digit = ${digit}"
// 关于Character.isDigit的更多示例:
Character.isDigit('a')
Character.isDigit(8)
Character.isDigit('8')
(2)变量和类型
// #3 模式匹配中变量的使用
dec(3)
val someStr = 'A' to 'Z'
someStr(10) match {
case 1 => "This is 1."
case '+' => "+ is +"
case xxx => s"xxx is ${xxx}" // 这里的变量xxx用于接收someStr(10)\
// case _ => "This is _" // xxx 接收了所有情况,因此不会到这一行,可以去掉
}
// #4 类型匹配
dec(4)
val value: Any = "hello Spark"
// Note: 进行类型匹配时,定义value时,要为Any类型,否则会报错
value match {
case i: Int => s"${i} is Int."
case s: String => s"${s} is String. "
case c: Char => s"${c} is Char. "
case _ => "_ is other type. "
}
(3)数组和列表
// #5 匹配数组
dec(5)
val a = Array(1, 2, 3)
a match {
case Array(0) => "一个元素的Array"
case Array(x, y) => "2个元素的Array,和为:" + x+y
case Array(x,y,z) => "3个元素的Array, 乘积为:" + x * y * z
case Array(x, _*) => "很多值的Array"
}
// #6 匹配List
dec(6)
val l = List(1,2,3,4,5,6)
l match {
case List(0) => "一个元素的List"
case List(x,y) => "2个元素的List,和为:" + x+y
case List(x,y,z) => "3个元素的List, 乘积为:" + x * y * z
case List(x,_*) =>"很多值的List"
}
2、样例类
(1)Case Class
- 在 Class 前面加上 case 这个普通类就变成了样例类
- 样例类可以进行模式匹配
// 演示 样例类 case class
def dec(no : Int): Unit = {
println("#" * 20 + s" e.g. #${no} " + "#" * 20)
}
// #0 常规的类, 不能用于模式匹配
dec(0)
class Grandpa(val occupation: String ) {
val familyName :String = "Xie"
}
class Father(override val occupation: String) extends Grandpa(occupation)
class Uncle(override val occupation: String) extends Grandpa(occupation)
val father = new Father("Lawyer")
father.occupation
father.familyName
father.isInstanceOf[Grandpa]
//father.isInstanceOf[Uncle]
// #1 case class 样例类, 是可以用于模式匹配的
dec(1)
class School
case class Netease(course:String) extends School
case class SimonXie(course:String) extends School
val test = new SimonXie("666");
// 这里相当于做了一次向上类型转换,用于类型匹配的判断
val bestSchool: School = SimonXie("Spark")
bestSchool match {
case Netease(course) => s"Netease ${course} is nice. "
case SimonXie(course) => s"Good to learn ${course} w/ ${bestSchool.getClass.getName}. "
case _ => "Others"
}
(2)Option 类型
- Option 类型用样例类来表示那种可能存在也可能不存在的值
- 样例子类 Some 包装了某个值
- 样例对象 None 表示没有值
// 演示 Option 类型
// Option类型 用样例类来表示那种可能存在也可能不存在的值
def dec(no:Int): Unit = println("#" * 20 + s" e.g. #${no} " + "#" * 20)
/**
* Option e.g. #1
* Map 的get方法他是返回的一个Option:
* - 如果给定的值没有就返回None;
* - 如果有值,就会被包含在Some里;
*/
dec(1)
val value = Map("k1" -> 1, "k2" -> 2)
value.get("k3")
value.get("k2") match {
case Some(v) => s"Has value ${v}. "
case None => "Has no this value. "
}
value.get("k3") match {
case Some(v) => s"Has value ${v}. "
case None => "Has no this value. "
}
// #2 Map.getOrElse
dec(2)
value.getOrElse("v3", 0)
value
/**
* e.g. #3
*/
dec(3)
val nov3 = value.+("k4"->4)
nov3.get("k3") // Option[Int] = None
for (x <- nov3.get("k3")) {
println(s"do nothing if x is None. x = ${x}")
}
for (x <- nov3.get("k4")) {
println(s"get value ${x}")
}
println("nov3-k2:"+ nov3.-("k2"))
for (x <- nov3.-("k2").get("k2")) {
println(s"do nothing if x is None. x = ${x}")
}
nov3
3、匿名函数
(1)值函数
在 scala 中,函数就和数值一样,可以在变量中存放函数,并作为参数传递给另一个函数。将一个函数复制给变量语法如下:
// val 变量 = 函数名 _
val v = func _
示例程序:
// 演示 - 值函数
def dec(no:Int): Unit = println("#" * 20 + s" e.g. #${no} " + "#" * 20)
// #1 将函数作为值赋值给变量
dec(1)
def func1(a : Int): Int = a
val vf1 = func1 _
vf1(1)
vf1(2)
// #2
dec(2)
import scala.math.ceil
val value = 3.14
def func2(a : Double) : Double = {
ceil(a) + 1.0
}
val vf2 = func2 _
vf2(value)
// #3
dec(3)
val vf3 = ceil _
vf3(value)
// #4
dec(4)
val func3: (Double) => Double = {
ceil
}
val vf4 = func3
vf4(value)
func3(value)
// #5
dec(5)
/**
* e.g. #5
* 这里演示的是如何将函数传递给另外一个函数
* map是直接接受的函数名作为参数
* 将数组中所有的元素分别应用到了传入的函数上
*/
Array(1.23, 2.34, 3.45).map(ceil)
Array(1.23, 2.34, 3.45).map(vf2)
(2)匿名函数
scala 中,无需给每个函数命名,这种函数叫匿名函数,可以将其存在变量中,也可以将其作为参数传递给另一个函数。
// 演示 匿名函数
def dec(no:Int): Unit = println("#" * 20 + s" e.g. #${no} " + "#" * 20)
val dec1 = (no:Int) => println("#" * 20 + s" e.g. #${no} " + "#" * 20) // 匿名函数
// #1 简单的匿名函数例子
//dec (1)
dec1(10)
(x : Double) => {
x * 2
}
res1(3)
// 我们可以将匿名函数赋值给变量f
val f = (x : Double) => x * 2
// 这样变量f包含了这个匿名函数,并可以当做函数来使用
f(4)
// #2 map 方法调用匿名函数,传入的参数是一个函数 function
dec(2)
/**
* e.g. #2 map方法 调用 匿名函数
* 无需命名, 就可以将匿名函数传递给另外一个函数
*/
Array(1.23, 2.34, 3.45).map((x:Double) =>{
x * 2
})
Array(1.23, 2.34, 3.45).map(f)
// [中置表示法],没有句点,这样的写法也比较常见:
Array(1.23, 2.34, 3.45) map(f)
4、带函数参数的函数
可以定义一些函数,它们可以接受函数作为参数。
// 常规函数的定义
def regularFunc(param1: Int):Int = {
param1
}
// 带函数参数的函数
// inputFunc: 传入参数类型、返回值类型均为Double的函数
def actionFunc(inputFunc: (Double) => Double,
param: Double) = {
inputFunc(param)
}
示例程序:
// 演示 - 带函数参数的函数
import scala.math.sqrt
val dec = (no:Int) => println("#" * 20 + s" e.g. #${no} " + "#" * 20) // 匿名函数
// 常规函数的定义
def regular(param : Int): Int = {
param
}
// #1
dec(1)
/**
* 定义一个带函数参数的函数
* @param inputFunc 函数的名字,类型是 (Double) => Double
* - 表示输入、输出类型均为 Doube 的函数
* @return
*/
def actionFunc(inputFunc : (Double) => Double) : Double = {
inputFunc(9)
}
sqrt(9)
actionFunc(sqrt)
// #2
dec(2)
/**
* 定义一个常规函数,接收2个参数
* @param id 整型
* @param name 字符串型
* @return 字符串型
*/
def sayHi(id : Int, name : String): String = {
s"My ID: ${id}, my name: ${name}"
}
def anotherFunc(inputFunc : (Int, String) => String,
id : Int,
name : String) : String = {
inputFunc(id, name)
}
// test
anotherFunc(sayHi, 1, "Simon")
5、闭包
- 闭包是函数的嵌套
- 内层函数可以访问外层函数中定义的变量
- 通常内层函数会使用匿名函数
- 外层函数的变量可以被 “保护” 起来
// 演示 - 闭包
// 定义了一个闭包函数,其返回值为函数体内的匿名函数
def enclosure(param : Int) : (Int) => Int = {
(x : Int) => x + param // 内层的匿名函数使用了外层函数的参数param
}
// 传入10的意义是param1 = 10
val f = enclosure(10)
// 此时f 为函数 (x :Int) => x + 10
// 传入闭包的param1 = 10 被 "保护"起来
f(3) // 此时的3 是匿名函数所需的x, 即x =3, 因此得到结果13
f(6)
f(88)
6、return 表达式
- 一般情况下,scala 无需显式使用 return 返回函数体的值,函数的最后一行就是返回值;
- return 用于有名函数里嵌套的内侧匿名函数,将返回值传递给外层的有名函数;
- Scala 里的匿名函数的 return 背后实际是通过特定的异常来实现的。
/**
* 结束匿名函数执行,并且在外层也结束并返回内层传递出来的返回值
* 演示 - return表达式
*/
object DemoReturn {
def main(args: Array[String]): Unit = {
val dec = (no:Int) => println("#" * 20 + s" e.g. ${no} " + "#" *20)
// #1
dec(1)
val testStr = "abcdefg"
/**
* 第一个例子, 有名函数 嵌套 匿名函数
* - 功能: 判断某个字符ch是否字符串str里
* @param str String
* @param ch Char
* @return 布尔型
*/
def chInStr(str : String, ch : Char) : Boolean = {
str.foreach(each => {
if (each == ch)
// 有名函数嵌套的匿名函数,可以利用return, 将返回值传递给外层的有名函数
return true
})
false // 默认返回值false
}
println(s"c in ${testStr}: " + chInStr(testStr, 'c'))
println(s"z in ${testStr}: " + chInStr(testStr, 'z'))
// #2
dec(2)
/**
* 第2个例子, 有名函数嵌套while循环
* - 功能: 返回ch在str中的下标位置
*
* @param str 字符串
* @param ch 字符
* @return 下标位置Int
*/
def indexOf(str:String, ch:Char) : Int = {
var i = 0
while (i != str.length) {
if (str(i) == ch)
return i
// 有名函数嵌套的while循环块内,可以利用return将返回值传递给外层有名函数
i += 1
}
-1 // 默认值-1,表示未找到
}
println(s"testStr=${testStr}, ch=d, index=${indexOf(testStr, 'd')}")
println(s"testStr=${testStr}, ch=x, index=${indexOf(testStr, 'x')}")
// #3
dec(3)
val list = List("A", "B", "C", "D")
// 补充例子, 匿名函数的return表达式是特殊的异常
// 这里没有将含有return表达式的匿名函数,嵌套在有名函数里
list.foreach(s => {
if (s == "C") {
println(s"Got ${s}, ok, do something.")
return
}
println(s"got ${s} , below return statement")
})
println("Last Line")
// 可以看到Last Line没有被打印,因为:
// 实质上,Scala里匿名函数的return表达式是特殊的异常
}
}
7、偏应用函数和偏函数
(1)偏函数
- 偏函数是一个单参数函数,且并未对该类型的所有值都实现相应处理逻辑
- 偏函数的字面量语法由包围在花括号中的一个或多个 case 语句构成
- 如果偏函数被应用,而函数的输入却与所有 case 语句不匹配,系统就会抛出一个 MatchError 的运行时错误。可以使用 isDefineAt 方法测试特定输入是否与偏函数匹配,这样偏函数就可以避免抛出 MatchError 错误。
// 演示 - 偏函数 partial function
val dec = (no:Int) => println("#" * 20 + s" e.g. ${no} " + "#" *20)
// #1
dec(1)
/**
* 构建一个偏函数,需要实现2个方法:
* 1. isDefinedAt
* 2. apply
*
* 利用 构建的偏函数pf,实现整型元素的过滤
*
* Note: 关于List的collect方法,可以参见scala-docs:
* Builds a new collection by applying a partial function
* to all elements of this list
* on which the function is defined.
*/
val pf = new PartialFunction[Any, Int] {
def isDefinedAt(any: Any) : Boolean = {
if (any.isInstanceOf[Int]) true
else false
}
def apply(any: Any) : Int = {
any.asInstanceOf[Int]
}
}
List(1,3,5,"test", 7) collect pf
pf.isDefinedAt("err")
pf.isDefinedAt(9)
// #2
dec(2)
/**
* 定义一个偏函数,实现对考试成绩的过滤,返回通过的成绩的结果
*/
val examPassed : PartialFunction[Int, String] = {
case x if x >= 60 => s"Exam passed, score is ${x}"
}
val examFailed: PartialFunction[Int, String] = {
case x if x < 60 => s"Exam failed, score is ${x}"
}
val scorePf: PartialFunction[String, Unit] = {
case x : String => println(x)
}
/*
* 如果偏函数被调用,而函数的输入却与所有case语句不匹配,系统
* 就会抛出一个MatchError的运行时错误
*
* 55~59的数据不满足case
*/
//(55 to 65 ) map examPassed foreach println
examFailed.isDefinedAt(55)
examPassed.isDefinedAt(100)
examFailed.isDefinedAt(60)
examPassed.isDefinedAt(60)
examPassed(60)
//examPassed(59) // 这里会报出异常scala.MatchError
// 多个偏函数连接在一起执行
(examPassed orElse examFailed)(30)
(examPassed andThen scorePf)(90)
(2)偏应用函数
偏应用函数是一个表达式,包含函数的部分而非全部参数列表。返回值是一个新函数,此函数携带剩下的参数列表。为了避免潜在的歧义,Scala 要求在后面加上下划线,用来告诉编译器使用目的。
import java.util.Date
/**
* 演示 - 偏应用函数 partial application
*/
object DemoPartialApp {
/**
* 定义一个通用的打印日志函数
*
* @param time 接收的时间
* @param data 传入的需要记录的log消息
*/
def logger(time: Date, data: String ): Unit = {
println(s"[${time}]: ${data}")
}
def main(args: Array[String]): Unit = {
val dec = (no:Int) => println("#" * 20 + s" e.g. ${no} " + "#" *20)
/**
* 定义一个休眠的匿名函数
*/
val nap = (x: Int) => { Thread.sleep(x * 1000)}
// #1
dec(1)
val time = new Date
logger(time, "line1")
nap(1) // 休眠1s
logger(time, "line2")
nap(1)
logger(time, "line3")
// #2
dec(2)
/**
* e.g. #2
*
* 使用偏应用函数,优化以上的功能
* - 绑定第一个time参数
* - 第二个参数使用下划线 _ 来替代缺失的参数列表
*/
val fixedTime = new Date
val fixTimeLogger = logger(fixedTime, _:String)
fixTimeLogger("line 4")
nap(1)
fixTimeLogger("line 5")
nap(1)
fixTimeLogger("line 6")
}
}
8、柯里化
柯里化指将原来接收2个参数的函数变成接收1个参数的函数;新的函数返回1个以原有第2个参数作为参数的函数。
// 演示 - 柯里化
val dec = (no:Int) => println("#" * 20 + s" e.g. ${no} " + "#" *20)
// #1
dec(1)
// 定义一个常规的匿名函数add
// 接收2个参数x和y
val add = (x:Int, y:Int) => {x+y}
add(3, 6)
// #2
dec(2)
val addKry = (x:Int) => {
y:Int => {x+y}
}
addKry(3) // 将代表内部匿名函数 (y:Int ) => 3 + y
addKry(3)(6)
// #3
dec(3)
// Scala支持如下的简写来定义柯里化函数:
def addKurry(x:Int)(y:Int) = x + y
addKurry(3)(6)
// #4
dec(4)
/**
* Array的corresponds方法,是柯里化的一个典型应用
* NOTE: 在scala-docs:
* def corresponds[B](that: GenSeq[B])(p: (T, B) ⇒ Boolean): Boolean
* Tests whether every element of this mutable indexed sequence
* relates to the corresponding element of another sequence by satisfying a test predicate.
*/
val a = Array("Hello","Scalla")
val b = Array("hello","scalla")
b.corresponds(a) ( (x, y) =>{
x.equalsIgnoreCase(y)
})
a.corresponds(b)(_.equalsIgnoreCase(_))
9、高阶函数
// 演示 - 一些高阶函数
// map函数, 将一个函数应用到某个集合的所有元素,并返回结果
(1 to 9).map( (x:Int) => 2 * x )
// 简写形式:
val mapRes = 1 to 9 map (2 * _)
println(s"map函数返回值: ${mapRes}")
/**
* foreach和map函数很类似,只不过它不返回任何结果
* 只是简单地讲一个函数,分别应用到集合里的每一个元素上
*/
val foreachRes = 1 to 9 foreach println
println(s"foreach函数返回值:${foreachRes}")
/**
* find 可以找到【第一个】符合条件的元素,而不是返回所有匹配项
*/
1 to 9 find(_>3)
1 to 8 find(_<0)
/**
* filter 可以用于过滤【所有】符合条件的匹配项
*/
1 to 9 filter(_>3)
/**
* zip 函数
*/
List(1, 2, 3, 4) zip List(5, 6, 7, 7)
/**
* flatten 可以将嵌套数据集进行 “展平”
*/
List(
List(1,2),
List(3,4)
).flatten
/**
* flatMap 相当于结合了map和flatten的功能:
*/
List(
List(1,2),
List(3,4)
).flatMap( _.map(_*2))