Scala 模式匹配详解
Posted hyunbar777
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Scala 模式匹配详解相关的知识,希望对你有一定的参考价值。
Scala 中的模式匹配类似于 Java 中的 switch 语法
int i = 10
switch (i)
case 10 :
System.out.println("10");
break;
case 20 :
System.out.println("20");
break;
default :
System.out.println("other number");
break;
但是 scala 从语法中补充了更多的功能,所以更加强大。
1 基本语法
- 模式匹配语法中,采用 match 关键字声明
- 每个分支采用 case 关键字进行声明
- 当需 要匹配时,会从第一个 case 分支开始
- 如果匹配成功,那么执行对应的逻辑代码
- 如果匹 配不成功,继续执行下一个分支进行判断。
- 如果所有 case 都不匹配,那么会执行 case _分支, 类似于 Java 中 default 语句
val a = 25
val b = 13
def matchDualOp(op: Char): Int = op match
case + => a + b
case - => a - b
case * => a * b
case / => a / b
case % => a % b
case _ => -1
println(matchDualOp(+))
println(matchDualOp(/))
println(matchDualOp(\\\\))
- 说明:
(1)如果所有 case 都不匹配,那么会执行 case _ 分支,类似于 Java 中 default 语句,
若此时没有 case _ 分支,那么会抛出 MatchError。
(2)每个 case 中,不需要使用 break 语句,自动中断 case。
(3)match case 语句可以匹配任何类型,而不只是字面量。
(4)=> 后面的代码块,直到下一个 case 语句之前的代码是作为一个整体执行,
可以 使用括起来,也可以不括。
2 模式守卫
- 说明
如果想要表达匹配某个范围的数据,就需要在模式匹配中增加条件守卫。
- 案例实操
def abs(num: Int): Int =
num match
case i if i >= 0 => i
case i if i < 0 => -i
println(abs(67))
3 模式匹配类型
3.1 匹配常量
def test(x: Any): String = x match
case 1 => "Int one"
case "hello" => "String hello"
case true => "Boolean true"
case + => "Char +"
case _ => ""
println(test("hello"))
println(test(+))
println(test(0.3))
String hello
Char +
3.2 匹配类型
需要进行类型判断时,可以使用前文所学的 isInstanceOf[T]和 asInstanceOf[T],也可使
用模式匹配实现同样的功能。
def test(x: Any): String = x match
case i: Int => "Int " + i
case s: String => "String " + s
//case list: List[String] => "List " + list
case array: Array[Int] => "Array[Int] " + array.mkString(",")
//泛型擦除
case m: List[_] => "List"+m
case a => "else: " + a
println(test(35))
println(test("hello"))
//泛型擦除
println(test(List("hi", "hello")))
//泛型擦除
println(test(List(2, 23)))
println(test(Array("hi", "hello")))
println(test(Array(2, 23)))
Int 35
String hello
List List(hi, hello)
List List(2, 23)
else: [Ljava.lang.String;@6e8dacdf
Array[Int] 2,23
3.3 匹配数组
scala 模式匹配可以对集合进行精确的匹配,例如匹配只有两个元素的、且第一个元素
为 0 的数组。
for (arr <- List(
Array(0),
Array(1, 0),
Array(0, 1, 0),
Array(1, 1, 0),
Array(2, 3, 7, 15),
Array("hello", 1, 30)
))
val result = arr match
case Array(0) => "0"
case Array(1, 0) => "Array(1, 0)"
// 匹配两元素数组
case Array(x, y) => "Array: " + x + ", " + y
case Array(0, _*) => "以0开头的数组"
case Array(x, 1, z) => "中间为1的三元素数组"
case _ => "else"
println(result)
0
Array(1, 0)
以0开头的数组
中间为1的三元素数组
else
中间为1的三元素数组
3.4 匹配列表
方式一:
for (list <- List(
List(0),
List(1, 0),
List(0, 0, 0),
List(1, 1, 0),
List(88),
List("hello")
))
val result = list match
case List(0) => "0"
case List(x, y) => "List(x, y): " + x + ", " + y
case List(0, _*) => "List(0, ...)"
case List(a) => "List(a): " + a
case _ => "else"
println(result)
0
List(x, y): 1, 0
List(0, ...)
else
List(a): 88
List(a): hello
方式二:
val list = List(1, 2, 5, 7, 24)
//val list = List(24)
list match
case first :: second :: rest =>
println(s"first: $first, second: $second, rest: $rest")
case _ => println("else")
first: 1, second: 2, rest: List(5, 7, 24)
3.5 匹配元组
for (tuple <- List(
(0, 1),
(0, 0),
(0, 1, 0),
(0, 1, 1),
(1, 23, 56),
("hello", true, 0.5)
))
val result = tuple match
case (a, b) => "" + a + ", " + b
//是第一个元素是 0 的元组
case (0, _) => "(0, _)"
//是第二个元素是 1 的元组
case (a, 1, _) => "(a, 1, _) " + a
case (x, y, z) => "(x, y, z) " + x + " " + y + " " + z
case _ => "else"
println(result)
a 12
b 35
c 27
a 13
a: 12
b: 35
c: 27
a: 13
3.6 匹配对象
object test
def main(args: Array[String]): Unit =
val student = Student("法外狂徒,张三", 18)
// 针对对象实例的内容进行匹配
val result = student match
case Student("法外狂徒,张三", 18) => "法外狂徒,张三, 18"
case _ => "Else"
println(result)
// 定义类
class Student(val name: String, val age: Int)
// 定义伴生对象
object Student
def apply(name: String, age: Int): Student = new Student(name, age)
// 必须实现一个unapply方法,用来对对象属性进行拆解
def unapply(student: Student): Option[(String, Int)] =
if (student == null)
None
else
Some((student.name, student.age))
-
val student = Student("法外狂徒,张三",11)
,该语句在执行时,实际调用的是 Student 伴生对象中的apply 方法,因此不用 new 关键字就能构造出相应的对象。 - 当将
Student("法外狂徒,张三", 11)
写在 case 后时[case Student("法外狂徒,张三", 11) => "法外狂徒,张三, 18"]
,会默认调用 unapply 方法(对象提取器),Student 作为 unapply 方法的参数,unapply 方法将 Student 对象的 name 和 age 属性提取出来,与 Student("法外狂徒,张三", 11)
中的属性值进行匹配 - case 中对象的 unapply 方法(提取器)返回 Some,且所有属性均一致,才算匹配成功,属性不一致,或返回 None,则匹配失败。
- 若只提取对象的一个属性,则提取器为
unapply(obj:Obj):Option[T]
- 若提取对象的多个属性,则提取器为
unapply(obj:Obj):Option[(T1,T2,T3…)]
- 若提取对象的可变个属性,则提取器为
unapplySeq(obj:Obj):Option[Seq[T]]
3.7 样例类
object test
def main(args: Array[String]): Unit =
val user = User("zhangsan", 18)
// 针对对象实例的内容进行匹配
val result = user match
case User("zhangsan", 18) => "zhangsan, 18"
case _ => "Else"
println(result)
// 定义样例类
case class User(name: String, age: Int)
- 样例类仍然是类,和普通类相比,只是其自动生成了伴生对象,并且伴生对象中自动提供了一些常用的方法,如:apply、unapply、toString、equals、hashCode 和 copy。
- 样例类是为模式匹配而优化的类,因为其默认提供了 unapply 方法,因此,样例类可以直接使用模式匹配,而无需自己实现 unapply 方法。
- 构造器中的每一个参数都成为 val,除非它被显式地声明为 var(不建议这样做)
查看字节码文件:
User$:
package com.duo;
import scala.None.;
import scala.Option;
import scala.Serializable;
import scala.Some;
import scala.Tuple2;
import scala.runtime.AbstractFunction2;
import scala.runtime.BoxesRunTime;
public final class User$ extends AbstractFunction2<String, Object, User>
implements Serializable
public static final MODULE$;
static
new ();
public final String toString()
return "User";
public User apply(String name, int age) return new User(name, age);
public Option<Tuple2<String, Object>> unapply(User x$0) return x$0 == null ? None..MODULE$ : new Some(new Tuple2(x$0.name(), BoxesRunTime.boxToInteger(x$0.age())));
private Object readResolve() return MODULE$;
private User$() MODULE$ = this;
User:
import scala.Function1;
import scala.Option;
import scala.Product;
import scala.Product.class;
import scala.Serializable;
import scala.Tuple2;
import scala.collection.Iterator;
import scala.reflect.ScalaSignature;
import scala.runtime.BoxesRunTime;
import scala.runtime.ScalaRunTime.;
import scala.runtime.Statics;
@ScalaSignature(bytes="\\006\\001\\005....")
public class User
implements Product, Serializable
private final String name;
private final int age;
public static Option<Tuple2<String, Object>> unapply(User paramUser)
return User..MODULE$.unapply(paramUser);
public static User apply(String paramString, int paramInt)
return User..MODULE$.apply(paramString, paramInt);
public static Function1<Tuple2<String, Object>, User> tupled()
return User..MODULE$.tupled();
public static Function1<String, Function1<Object, User>> curried()
return User..MODULE$.curried();
public String name()
return this.name;
public int age() return this.age;
public User copy(String name, int age) return new User(name, age);
public String copy$default$1() return name();
public int copy$default$2() return age();
public String productPrefix() return "User";
public int productArity()
return 2;
public Object productElement(int x$1)
int i = x$1;
switch(i)
default:
throw new IndexOutOfBoundsException(BoxesRunTime.boxToInteger(x$1).toString());
case 1:
break;
case 0:
return name();
public Iterator < Object > productIterator ()
return ScalaRunTime..MODULE$.typedProductIterator(this);
public boolean canEqual(Object x$1)
return x$1 instanceof User;
public int hashCode()
int i = -889275714; i = Statics.mix(i, Statics.anyHash(name())); i = Statics.mix(i, age()); return Statics.finalizeHash(i, 2);
public String toString()
return ScalaRunTime..MODULE$._toString(this);
public boolean equals(Object x$1)
if (this != x$1)
Object localObject = x$1;
int i;
if ((localObject instanceof User)) i = 1; else i = 0;
if (i == 0) break label96;
User localUser = (User) x$1;
str = localUser.name();
String tmp42_32 = name();
if (tmp42_32 == null)
tmp42_32; if (str == null) break label63; tmpTernaryOp = tmp42_32; break label88;
public User (String name, int age)
Product
.class.$init$(this);
4 变量声明中的模式匹配
val (x, y) = (10, "hello")
println(s"x: $x, y: $y")
val List(first, second, _*) = List(23, 15, 9, 78)
println(s"first: $first, second: $second")
val fir :: sec :: rest = List(23, 15 , 9, 78)
println(s"first: $fir, second: $sec, rest: $rest")
5 for推导式中进行模式匹配
val list: List[(String, Int)] = List(("a", 12), ("b", 35), ("c", 27), ("a", 13))
// 1 原本的遍历方式
for (elem <- list)
println(elem._1 + " " + elem._2)
// 2 将List的元素直接定义为元组,对变量赋值
for ((word, count) <- list )
println(word + ": " + count)
// 3 可以不考虑某个位置的变量,只遍历key或者value
for ((word, _) <- list)
println(word)
// 4 可以指定某个位置的值必须是多少
for (("a", count) <- list)
println(count)
// 5 循环守卫
for ((k, v) <- map if v >= 1)
println(k + " ---> " + v)
6 偏函数中的模式匹配
偏函数也是函数的一种,通过偏函数我们可以方便的对输入参数做更精确的检查。例如
该偏函数的输入类型为 List[Int],而我们需要的是第一个元素是 0 的集合,这就是通过模式
匹配实现的
(1)偏函数定义
val second: PartialFunction[List[Int], Option[Int]] =
case x :: y :: _ => Some(y)
(2)偏函数原理
上述代码会被 scala 编译器翻译成以下代码,与普通函数相比,只是多了一个用于参数
检查的函数——isDefinedAt,其返回值类型为 Boolean。
val second = new PartialFunction[List[Int], Option[Int]]
//检查输入参数是否合格
override def isDefinedAt(list: List[Int]): Boolean = list match
case x :: y :: _ => true
case _ => false
//执行函数逻辑
override def apply(list: List[Int]): Option[Int] = list match
case x :: y :: _ => Some(y)
(3)偏函数使用
偏函数不能像 second(List(1,2,3))这样直接使用,因为这样会直接调用 apply 方法,而应
该调用 applyOrElse 方法,如下
second.applyOrElse(List(1,2,3), (_: List[Int]) => None)
applyOrElse 方法的逻辑为 `if (ifDefinedAt(list)) apply(list) else default。如果输入参数满
足条件,即 isDefinedAt 返回 true,则执行 apply 方法,否则执行 defalut 方法,default 方法
为参数不满足要求的处理逻辑。
(4)案例实操
讲list中元组第二个元素*2
val list: List[(String, Int)] = List(("a", 12), ("b", 35), ("c", 27), ("a", 13))
// 1. map转换,实现key不变,value2倍
val newList = list.map( tuple => (tuple._1, tuple._2 * 2) )
// 2. 用模式匹配对元组元素赋值,实现功能
val newList2 = list.map(
tuple =>
tuple match
case (word, count) => (word, count * 2)
)
// 3. 省略lambda表达式的写法,进行简化
val newList3 = list.map
case (word, count) => (word, count * 2)
以上是关于Scala 模式匹配详解的主要内容,如果未能解决你的问题,请参考以下文章