模式匹配
Scala中的模式匹配类似于 Java中的switch语法,但是scala从语法中补充了更多的功能,可以按照指定的规则对数据或对象进行匹配,所以更加强大。
1. 基本语法
模式匹配语法中,采用match
关键字声明,每个分支采用case
关键字进行声明,当需要匹配时,会从第一个case
分支开始,如果匹配成功,那么执行对应的逻辑代码,如果匹配不成功,继续执行下一个分支进行判断。如果所有case
都不匹配,那么会执行case _
分支,类似于Java中default语句。scala中没有default关键字。如果没有匹配成功,就会报错。这一点和Java中不一样。
scala
def main(args: Array[String]): Unit = {
val age = 10
age match {
case 10 => println("结果为10")
case 20 => println("结果为20")
case _ => println("结果为其他")
}
}
运行结果:
✍️总结_(下划线)的使用场景
- 导包使用import
比如import xxx.yyy._
- 可以作为标识符
比如val _ = "张三"
- 可以将函数作为对象使用
比如val obj = fun _
- 如果匿名函数只使用一次,那么采用下划线代替
比如_+_
- Import类时,使用下划线代表java中的星号
比如import java.util._
- Import类时,用于屏蔽类
比如import java.util.{Date=>_}
- 对象属性初始化
比如var name: String = _
- 模式匹配时表示任意值
比如case _ => {}
- 模式匹配时泛型用下划线表示任意类型
比如case s: List[_] =>{}
2. 匹配常量
Scala中,模式匹配可以匹配所有的字面量,包括字符串,字符,数字,布尔值等等。
scala
def main(args: Array[String]): Unit = {
println(describe(5))
println(describe("hello"))
println(describe(true))
println(describe('+'))
println(describe(false))
}
def describe(x: Any): String = {
x match {
case 5 => "Int five"
case "hello" => "Starting hello"
case true => "Boolen true"
case '+' => "Char +"
}
}
运行结果:
3. 匹配类型
需要进行类型判断时,可以使用前文所学的isInstanceOf[T]
和asInstanceOf[T]
,也可使用模式匹配实现同样的功能。
scala
def main(args: Array[String]): Unit = {
println(describe(5))
println(describe("test"))
// 如果所有的规则都不匹配,会查找下划线分支,
// 如果想要使用下划线代表的值,一般会给下划线取个别名
println(describe(false))
println(describe(Array(1, 4, 7, 8)))
println(describe(Array("1", "4", "7", "8")))
println(describe(List(1,2,3,5)))
// 类型匹配时,泛型不会被匹配,特殊的Array除外,因为Array不是真正的泛型
// 真正的泛型只会在编译时有效,而Array的泛型是在运行时也有效,可以理解成Array[Int]它是一个整体类型
println(describe(Set("hello", "test")))
// 如果case直接后面写类型,并不表示类型匹配,而是对象匹配
// 这样写会被编译器认为匹配伴生对象
println(describe(true))
}
def describe(x: Any): String = {
x match {
case i: Int => "Int"
case b: Boolean => "Boolean"
case s: String => "String hello"
case m: List[_] => "List"
case c: Array[Int] => "Array[Int]"
case d: Set[Int] => "Set[Int]"
case something => "something else "+something
}
}
运行结果:
4. 匹配数组
scala 模式匹配可以对集合进行精确的匹配,例如匹配只有两个元素的、且第一个元素为0的数组。
scala
def main(args: Array[String]): Unit = {
for (arr <- Array(
Array(1),
Array(1, 0),
Array(0, 1, 1),
Array(1,2,4,6),
Array("hello", 1)
)){
val result = arr match {
case Array(1)=> "1"
case Array(x, y) => x+" "+y
case Array(0, _*) => "以0开头的数组"
case _ => "something else"
}
println(result)
}
}
运行结果:
5. 匹配列表
scala
def main(args: Array[String]): Unit = {
// 方式一
for (arr <- List(
List(0),
List(1, 0),
List(0, 0, 0),
List(1,0,0),
List(88)
)){
val result = arr match {
case List(0) => "0" //匹配 List(0)
case List(x, y) => x + "," + y //匹配有两个元素的 List
case List(0, _*) => "0 ..."
case _ => "something else"
}
println(result)
}
println("*******************************")
// 方式二
val list: List[Int] = List(1, 2, 5, 6, 7)
list match {
// 要求列表含有三个元素
// 特殊的List(1,2)也满足条件,因为可以分为1::2::Nil
case first :: second :: rest => println(first + "-" + second + "-" + rest)
case _ => println("something else")
}
}
运行结果:
6. 匹配元组
scala
def main(args: Array[String]): Unit = {
//对一个元组集合进行遍历
for (tuple <- Array(
(0, 1),
(1, 0),
(1, 1),
(1, 0, 2),
(1, 0, 2, 0)
)) {
val result = tuple match {
case (0, _) => "0 ..." //是第一个元素是 0 的元组
case (y, 0) => y + "0" // 匹配第二个元素是 0 的对偶元组
case (a, b) => a + " " + b
case _ => "something else" //默认
}
println(result)
}
}
运行结果:
7. 模式匹配的应用
7.1 变量声明中的模式匹配
Scala的模式匹配不仅仅应用在match使用,绝对不止Java中switch的常量匹配的强大。
scala
def main(args: Array[String]): Unit = {
val user = (1, "jack", 32)
println(user._1)
println(user._2)
println(user._3)
// 模式匹配可以简化
// 给元组元素命名
val (id, name, age) = user
println(s"id: $id, name: $name, age: $age")
}
运行结果:
7.2 for表达式中的模式匹配
scala
def main(args: Array[String]): Unit = {
val dataMap = Map("1"-> 32, "2"-> 44)
for (elem <- dataMap) {
if(elem._2==44){
println(elem._1 +" : "+elem._2)
}
}
// 可以简化不用序列号
// 告诉编译器,只能匹配上value为44
for((k, 44) <- dataMap){
println(k+" : "+44)
}
println("&&&&&&&&&&&&&&&&&&&&&&&&&")
// 统计不同省份的物品下单数量
val list1 = List(
(("河南", "鞋"), 4),
(("河南", "裤子"), 1),
(("成都", "鞋"), 3),
(("河南", "衣服"), 2),
(("成都", "裤子"), 3)
)
// 代码中序列化用的太多,不太清晰逻辑
list1.map(
item => {
(item._1._1, (item._1._2, item._2))
}
).foreach(println)
println("&&&&&&&&&&&&&&&&&&&&&&&&&")
// 使用模式匹配
// 小括号在匿名函数中表示参数列表,所以无法直接作为模式匹配的元组规则
// 使用case明确告诉编译器这里使用模式匹配,再将小括号变成大括号
list1.map{
case ((pri, goods), num) =>{
(pri, (goods, num))
}
}.foreach(println)
}
运行结果:
8. 匹配对象
scala
def main(args: Array[String]): Unit = {
val user: User = getUserFromDB()
// 判断得到的用户是否是我们想要的30岁用户
user match {
// 普通的对象无法再模式匹配中使用,因为对象的匹配其实就是属性的匹配
// 需要通过对象获得其属性, 其方法是:unapply
// 它会将对象的每个属性进行值比较,完全相同才成立匹配成功
case User("jack", 30)=>{
println("这就是我们想要的用户")
}
case _ =>{
println("没找到用户")
}
}
}
class User {
var name: String = _
var age: Int = _
}
object User{
def apply(name: String, age: Int): User= {
val user = new User()
user.age = age
user.name = name
user
}
// unapply方法将user对象的name和age属性提取出来,形成tuple
def unapply(user: User): Option[(String, Int)] ={
Option((user.name, user.age))
}
}
def getUserFromDB(): User={
// 实际调用的是User伴生对象中的apply方法,因此不用new就能构造出相应的对象
User("jack", 30)
}
运行结果: 从上面看对象匹配比较麻烦,需要额外添加特定的方法才能实现, Scala额外提供了一个简单的实现方式进行匹配。使用样例类进行实现, 在类的前面加上关键字case。
9. 样例类
9.1 样例类说明
- 样例类的构造参数会自动作为类的属性,默认不能改变,使用val声明的,如果想要改,需要使用var。
- 样例类就是一个普通类,只不过可以应用在模式匹配中用于匹配对象。
- 样例类声明很简单,但是编译后会自动产生大量的方法,都是由编译器完成。和普通类相比,只是其自动生成了伴生对象,并且伴生对象中自动提供了一些常用的方法,如apply、unapply、toString、equals、hashCode和copy。
9.2 使用样例类
用样例类会节省大量代码。
scala
def main(args: Array[String]): Unit = {
val user: MyUser = getUserFromDB()
// 判断得到的用户是否是我们想要的30岁用户
user match {
case MyUser("jack", 30) => {
println("这就是我们想要的用户")
}
case _ => {
println("没找到用户")
}
}
}
// 声明样例类
case class MyUser(name: String, var age: Int)
def getUserFromDB(): MyUser = {
MyUser("jack", 30)
}
运行结果:
10. 偏函数
偏函数也是函数的一种,和全函数对比来说,全函数就是对集合所有的数据进行处理。所谓偏函数表示对满足条件的数据进行处理。
scala
def main(args: Array[String]): Unit = {
val list = List(1, 2, "3", 4)
// 数字加1 ,字符串不要
list.filter(_.isInstanceOf[Int]).map{
case i:Int=> i+1
}.foreach(println)
println("*************************")
// 使用偏函数直接实现
// map不支持偏函数
list.collect{
case i:Int=> i+1
}.foreach(println)
}
运行结果:
如何查看那些方法支持偏函数呢?
比如上文中的collect方法,查看源码: