Scala模式匹配

Scala模式匹配

 

 

 

 

 

模式匹配简介

 

简介

Scala模式匹配机制十分强大,可以应用在很多场合:switch语句、类型查询,以及“析构”(获取复杂表达式中不同的部分)。除此之外,Scala还提供了样例类,对模式匹配进行了优化。

 

要点

  • match表达式是一个更好的switch,不会有意外掉入下一个分支的问题。
  • 如果没有模式能够匹配,会抛出MatchError。可以用case_模式来避免。
  • 模式可以包含一个随意定义的条件,称作守卫。
  • 你可以对表达式的类型进行匹配;优先选择模式匹配而不是isInstanceOf
  • 你可以匹配数组、元组和样例类的模式,然后将匹配到的不同部分绑定到变量。
  • 在for表达式中,不能匹配的情况会被安静的跳过。
  • 样例类是编译器会为之自动产出模式匹配所需方法的类。
  • 样例类继承层级中公共超类应该是sealed的。
  • 用Option来存放对于可能存在也可能不存在的值——这比null更安全。

 

 

 

 

语法格式

Scala一个模式匹配包含了一系列备选项,每个都开始于关键字case。每个备选项都包含了一个模式以及一到多个表达式。箭头符号=>隔开了模式和表达式。

代码示例:

object Test {
  def main(args: Array[String]) {
    println(matchTest(3))

  }

  def matchTest(x: Int): String = x match {
    case 1 => "one"
    case 2 => "two"
    case _ => "many"
  }
}



守卫

代码示例:

ch match {
  case '+' => sign = 1
  case '-' => sign = -1
  case _ if Character.isDigit(ch) => digit = Character.digit(ch, 10)
  case _ => sign = 0
}

注意:模式总是从上往下进行匹配。如果带守卫的这个模式不能匹配,则捕获所有的模式(case _) 会被用来尝试进行匹配。

 

 

  

类型匹配

你可以对表达式的类型进行匹配,例如:

 

//可以接受任意类型的对象
def generalType(obj: Any) = obj match {
  //如果对象是Int类型,则把对象的值赋给x然后执行后的打印语句
  case x: Int => println("Int : " + x)
  //如果对象是String类型,则把对象的值赋给s然后执行后面的语句
  case s: String => println("String : " + s)
  case _: BigInt => println("BigInt")
  //如果是除了上面类型外的其他类型,则直接输出"other"
  case _ => println("other")
}


在Scala中,我们更倾向于使用这样的模式匹配,而不是使用isInstanceOf操作符。

注意:

  • 模式中的变量名,匹配到的值将会被绑定到相应的变量。
  • 匹配发生在运行期,Java虚拟机中泛型的类型信息是被擦掉的。因此,你不能用类型匹配来匹配特定的Map类型。

例如:

case m: Map[String, Int] => ...  //这样做是不可取的

你可以匹配一个通用的映射:

case m: Map[_, _] => ...  //OK

但是,对于数组而言元素的类型时完好的。你可以匹配到Array[Int]

 


不同类型数据的模式匹配:

object Test {
  def main(args: Array[String]) {
    println(matchTest("two"))
    println(matchTest("test"))
    println(matchTest(1))
    println(matchTest(6))

  }

  def matchTest(x: Any): Any = x match {
    case 1 => "one"
    case "two" => 2
    case y: Int => "scala.Int"
    case _ => "many"
  }
}



 

匹配数组、列表和元组

数组匹配

val array: Array[Int] = Array[Int](1, 2, 3, 4, 5, 6, 7, 8)

array match {
    //匹配包含0的数组
  case Array(0) => "0"
    //匹配任何带有两个元素的数组,并绑定值到变量x,y
  case Array(x, y) => x + " " + y
    //匹配任何以零开始的数组
  case Array(0, _*) => "0 ......"
  case _ => "Something else"
}


 

列表匹配

你可以使用数组匹配的方法匹配列表,使用List表达式。或者,你也可以使用::操作符:

val list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8)

list match {
  //匹配包含0的列表
  case 0 :: Nil => "0"
  //匹配任何带有两个元素的列表,并绑定值到x,y
  case x :: y :: Nil => x + " " + y
  //匹配任何以0开头的列表
  case 0 :: tail => "0 ..."
  case _ => "Something else"
}


元组匹配

val tuple = Tuple8(1, 2, 3, 4, 5, 6, 7, 8)

tuple match {
  //匹配包含0的列表
  case (0, _) => "0"
  //匹配任何带有两个或以上元素的列表,并绑定值到x,y
  case (x, y, _) => x + " : " + y
  case _ => "Something else"


 

变量声明中的模式

经过上面的学习,你看到了模式是可以带变量的。你也可以在变量声明中使用这样的模式。

例如:

val (x, y) = (1, 2)  //把1,2绑定到变量x,y

这样同时把x定义为1,把y定义为2。这对于那些返回对偶的函数而言很有用。

例如:

val (q, r) = BigInt(10) /% 3   //返回商和余数,并绑定值到q,r

/%方法返回包含商和余数的对偶,而这两个值分别被变量q和r捕获到。

 

数组的变量绑定:

val arr: Array[Int] = Array(1,2,3,4,5,6,7,8)
val Array(first, second, _*) = arr  //绑定数组中第一,二个元素给first,second


 

for表达式中的模式

你可以在for推导式中使用变量的模式。对每一个遍历到的值,这些变量都会被绑定。这使得我们可以方便地遍历映射:

 

import scala.collection.JavaConversions.propertiesAsScalaMap
//将Java的Properties转换为Scala映射
for ((k, v) <- System.getProperties()) {
  println(k + " -> " + v)
}


对映射中的每一个(键,值)对偶,k被绑定到键,而v被绑定到值。

在for推倒式中,失败的匹配将被安静地忽略。举例来说,如下循环将打印出所有值为空的键,跳过其他键:

for ((k, "") <- System.getProperties()) {
  println(k)
}

另一种写法(使用守卫)

for ((k, v) <- System.getProperties() if v == "") {
  println(k)
}


 

Scala提取器

示例

提取器是从传递给它的对象中提取出构造该对象的参数。

Scala 提取器是一个带有unapply方法的对象。unapply方法算是apply方法的反向操作:unapply接受一个对象,然后从对象中提取值,提取的值通常是用来构造该对象的值。

以下实例演示了邮件地址的提取器对象:

object Test {
  def main(args: Array[String]) {

    println("Apply 方法 : " + apply("Zara", "gmail.com"));
    println("Unapply 方法 : " + unapply("Zara@gmail.com"));
    println("Unapply 方法 : " + unapply("Zara Ali"));

  }

  // 注入方法 (可选)
  def apply(user: String, domain: String) = {
    user + "@" + domain
  }

  // 提取方法(必选)
  def unapply(str: String): Option[(String, String)] = {
    val parts = str split "@"
    if (parts.length == 2) {
      Some(parts(0), parts(1))
    } else {
      None
    }
  }
}

打印结果:

Apply 方法 : Zara@gmail.com
Unapply 方法 : Some((Zara,gmail.com))
Unapply 方法 : None

以上对象定义了两个方法: apply 和 unapply 方法。通过 apply 方法我们无需使用 new 操作就可以创建对象。所以你可以通过语句 Test("Zara", "gmail.com") 来构造一个字符串 "Zara@gmail.com"。

unapply方法算是apply方法的反向操作:unapply接受一个对象,然后从对象中提取值,提取的值通常是用来构造该对象的值。实例中我们使用 Unapply 方法从对象中提取用户名和邮件地址的后缀。


 

提取器模式匹配

在我们实例化一个类的时,可以带上0个或者多个的参数,编译器在实例化的时会调用 apply 方法。我们可以在类和对象中都定义 apply 方法。

就像我们之前提到过的,unapply 用于提取我们指定查找的值,它与 apply 的操作相反。 当我们在提取器对象中使用 match 语句是,unapply 将自动执行,如下所示:

object Test {
  def main(args: Array[String]) {

    val x = Test(5)
    println(x)

    x match {
      case Test(num) => println(x + " 是 " + num + " 的两倍!")
      //unapply 被调用
      case _ => println("无法计算")
    }

  }

  def apply(x: Int) = x * 2

  def unapply(z: Int): Option[Int] = if (z % 2 == 0) Some(z / 2) else None
}

打印结果:

10
10 是 5 的两倍!

 

 

样例类

使用了case关键字的类定义就是就是样例类(case classes),样例类是种特殊的类,经过优化以用于模式匹配。

以下是样例类的简单实例:

object Test {
  def main(args: Array[String]) {
    val alice = new Person("Alice", 25)
    val bob = new Person("Bob", 32)
    val charlie = new Person("Charlie", 32)

    for (person <- List(alice, bob, charlie)) {
      person match {
        //匹配姓名为Alice,年龄为25的Person对象
        case Person("Alice", 25) => println("Hi Alice!")
        //匹配姓名为Bob,年龄为32的Person对象
        case Person("Bob", 32) => println("Hi Bob!")
        //匹配剩下的任何有且仅有name和age属性的Person对象
        case Person(name, age) =>
          println("Age: " + age + " year, name: " + name + "?")
      }
    }
  }

  // 样例类
  case class Person(name: String, age: Int)

}

在声明样例类时,下面的过程自动发生了:

  1. 构造器的每个参数都成为val,除非显式被声明为var,但是并不推荐这么做;
  2. 在伴生对象中提供了apply方法,所以可以不使用new关键字就可构建对象;
  3. 提供unapply方法使模式匹配可以工作;
  4. 生成toString、equals、hashCode和copy方法,除非显示给出这些方法的定义。

 

 


模拟枚举

样例类让你可以在Scala中模拟出枚举类型

代码示例:

object exam1 {
  def main(args: Array[String]): Unit = {

    //定义父抽象类
    sealed abstract class TrafficLightColor
    //创建样例类对象
    case object Red extends TrafficLightColor
    case object Yellow extends TrafficLightColor
    case object Green extends TrafficLightColor

    //循环打印匹配结果
    val colors = List(Red, Yellow, Green)
    for (color <- colors) {
      println(matchColor(color))
    }

    //定义函数演示——模拟枚举
    def matchColor(color: Any): String = color match {
      case Red => "stop"
      case Yellow => "hurry up"
      case Green => "go"
      case _ => "something Wrong"
    }
  }
}

打印结果:

stop
hurry up
Go


 

Option类型

标准库中的Option类型用样例类来表示那种可能存在、也可能不存在的值。样例子类Some包装了某个值,例如:Some(“Fred”)。而样例对象None则表示没有值。

Option支持泛型。举例来说,Some(“Fred”)的类型为Option[String]。

 

Map类的get方法返回一个Option。如果对于给定的键没有值,则get返回None,如果有值,则将结果包在Some中返回。

val scores = Map(("Alice", 88), ("Cindy", 71), ("Bob", 94))
scores.get("Alice") match {
  case Some(score) => println(score)
  case None => println("No Score")
}

打印结果:

88



偏函数

被包含在花括号内的一组case语句是一个偏函数——一个并非对所有输入值都有定义的函数。它是PartialFunction[A,B]类的一个实例。(A为参数类型,B为返回值类型)。该类有两个方法:apply方法从匹配到的模式计算函数值,而isDefinedAt方法在输入至少匹配其中一个模式时返回true。

代码示例:

val f: PartialFunction[Char, Int] = {
  case '+' => 1;
  case '-' => -1
}
//调用f.apply('-'),返回 -1
println(f('-'))
//返回false
println(f.isDefinedAt('0'))
//抛出MatchError异常
println(f('0'))


 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值