Scala语言基础(八)模式匹配

1. 模式匹配

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

1.1 switch

Scala中C风格switch语句的等效代码:

var sign = …
var ch: Char = …
ch match {
case ‘+’ = sign = 1
case ‘-’ = sign = -1
case _ = sign = 0’
}

case _ 模式是捕获所有情况,避免代码抛出MatchError.

与if类似,match也是表达式,而不是语句。前面的代码可以简化为:

sign = ch match {
case ‘+’ => 1
case ‘-’ => -1
case _ => 0’
}

可以在match表达式中使用任何类型,而不仅仅是数字。例如:

        val array = Array("red", "green", "yellow")

        val color = array(new Random().nextInt(3))

        val sign = color match {
            case "red" => "红灯"
            case "green" => "绿灯"
            case _ => "黄灯"
        }

        println(sign)

1.2 守卫

假如我们向扩展我们的示例以匹配所有数字。可以给模式添加守卫,就像这样:

        var sign = 1
        val ch = 1

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

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

1.3 模式中的变量

如果case 关键字后面跟着一个变量名,那么匹配的表达式会被赋值给哪个变量。例如:

        var sign = 1
        val ch = 1

        ch match {
            case '+' => sign = 1
            case '-' => sign = -1
            case ch => sign = ch.toInt+100
        }

1.4 类型模式

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

        val obj: Any = "666"

        val result = obj match {
            case x: Int => x
            case s: String => Integer.parseInt(s)
            case _: BigInt => Int.MaxValue
            case _ => 0
        }

在Scala中,使用这种模式匹配比isInstanceOf操作符更方便。当匹配类型时,必须给出一个变量名称。
注意:匹配发生在运行期,Java虚拟机中的泛型类型信息是被擦掉的,因此不能用类型来匹配特定的Map类型。

case m:Map[String, Int] => ... //别这样做!

//可以匹配一个通用的映射:
case m:Map[_, _] => ... 

对于数组而言元素的类型信息是完好的。可以匹配到Array[Int].

1.5 匹配数组、列表和元组

要匹配数组的内容,可以在模式中使用Array表达式:

        val arr = Array(0,2)

        val result = arr match {
            case Array(0) => "0000"
            case Array(x, y)=> x+" " +y
            case Array(0, _*) => "多元素数组"
            case _ => "something else"
        }

第一个模式匹配只有一个0元素的数组。第二个模式匹配任何带有两个元素的素组,并将这两个元素分别绑定到变量x和y。第三个表达式匹配任何以零开始的素组。

可以以同样的方式匹配列表,使用List表达式。或者,也可以使用::操作符:

        val lst = List(0, 2)

        val result = lst match {
            case 0 :: Nil => "0000"
            case x :: y :: Nil => x + " " + y
            case 0 :: tail => "多元素列表"
            case _ => "something else"
        }

对于元组,可以在模式中使用元组表示法:

        val pair = (5, 0)

        val result = pair match {
            case (0, _) => "0000"
            case (y, 0) => y + " " + 0
            case _ => "something else"
        }

1.6 提取器

模式匹配数组、列表和元组,这些功能的背后是提取器(extractor)机制——带有从对象中提取值的unapply或unapplySeq方法的对象。前者用于提取固定数量值的对象,后者提取是一个序列,可长可短。

例如:

arr match {
	case Array(0, x) => ...
	...
}

Array伴生对象就是一个提取器——它定义了一个unapplySeq方法。该方法被调用时,是以被执行匹配动作的表达式作为参数,而不是模式中看上去像是参数的表达式。Array.unapplySeq(arr) 产出一个序列的值,即数组中的值。第一个值与0进行比较,而第二个值被赋值给x.

正则表达式是另一个适合使用提取器的场景。如果正则表达式有分组,可以用提取器来匹配每个分组。例如:

val pattern = "([0-9]+) ([a-z]+)".r
"99 bottles" match {
	case pattern(num, item) =>...
	// 将num设为“99”, item设为“bottles"
}

pattern.unapplySeq(“99 bottles”)产出一系列匹配分组的字符串。这些字符串被分别赋值给了变量num和item.
注意:在这里提取器并非是一个伴生对象,而是一个正则表达式对象。

1.7 变量声明中的模式

变量声明中也可以使用模式匹配,例如:

val (x, y) = (1, 2)

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

val (q, r) = BigInt(10) /% 3

/%方法返回包含商和余数的对偶,而这两个值分别被变量q和r捕获到。同样的语法也可以用于带有变量的模式。例如:

val Array(first, second, _*) = arr

上述代码将数组arr的第一个和第二个元素分别赋值给first和second。

1.8 for表达式中的模式

可以在for推导式中使用带变量的模式。对于每一个遍历到的值,这些变量都会被绑定,例如:

        val map = Map(
            "a" -> 1,
            "b" -> 2,
            "c" -> 3,
            "d" -> 4
        )

        for ((k, v) <- map) {
            println(k + " -> " + v)
        }

1.9 样例类

样例类是一种特殊的类,他们经过优化以被用于模式匹配,例如:

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

//        val amt = Dollar(12)
        val amt = Currency(4,"hh")

        val result = amt match {
            case Dollar(v) => "$" + v
            case Currency(_, u) => "Oh noes, I got " + u
            case Nothing => ""
        }

        println(result)
    }
}

abstract class Amount

case class Dollar(value: Double) extends Amount

case class Currency(value: Double, unit: String) extends Amount

case object Nothing extends Amount

声明样例类时,有如下几件事自动发生:

构造器中的每个参数都成为val——除非它被显式地声明为var(不建议这样做)本质是val修饰的参数只有getter方法,而var修饰的参数有getter和setter方法。
伴生对象中提供apply方法让你不用new关键字就能构造出相应的对象。
提供unapply方法让模式匹配可以工作。
将生成toStirng、equals、hashCode和copy方法。

1.10 copy方法和带名参数

样例类的copy方法创建一个与现有对象相同的新对象。例如:

        val amt = Currency(4,"hh")
        val price = amt.copy()
        println(amt) // Currency(4.0,hh)
        println(price) // Currency(4.0,hh)
        println(amt==price) // true
        println(amt.equals(price)) // true

还可以用带名参数来修改某些属性:

        val amt = Currency(4, "hh")
        val price = amt.copy(value = 20.1)
        val price2 = amt.copy(unit = "CHF")
        println(price, "---", price2) // (Currency(20.1,hh),---,Currency(4.0,CHF))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值