<< ..

Scala模式匹配

发布时间:

本文依据hongjiang.info话说模式匹配系列而来,是个人对于scala模式匹配的学习笔记

要想理解模式匹配 pattern-matching,需要拆开理解

(全篇都是)前菜 —— 模式

模式是数据结构上,用于描述一个结构的组成。英文单词pattern和正则的含义相近,不过适用范围不仅仅局限于字符串,扩展到各种类型的数据结构。比如正则表达式里 “^A.” 这个pattern 表示以A开头、后续一个或多个字符组成的字符串;List(“A”, _, _) 也是个pattern,表示第一个元素是”A”,后续一个或多个元素的List。

直观的看几个例子:

// match...case...内匹配数组Array(1,2,3),匹配成功返回print
scala> Array(1,2,3) match { case Array(1,2,3) => println("ok") }
ok

// 将一个错误的数组match最终返回MatchError
scala> Array(1,3,3) match { case Array(1,2,3) => println("ok") }
scala.MatchError: [I@28ec166e (of class [I)
  ... 32 elided

// match一个元素1开头的数组
scala> Array(1,2,3) match { case Array(1, _*) => println("ok") }
ok

scala> Array(2,2,3) match { case Array(1, _*) => println("ok") }
scala.MatchError: [I@1eea9d2d (of class [I)
  ... 32 elided

// 指定首尾元素,中间元素任意
scala> List("A", "C", "C") match { case List("A", _, "C") => println("ok") }
ok

scala> List("A", 123, "C") match { case List("A", _, "C") => println("ok") }
ok

scala> val a = 100
a: Int = 100

scala> a match { case 100 => println("ok") }
ok

scala> a match { case _:Int => println("ok") }
ok

上述例子中case后面的都是模式,表示指定元素组成的某种类型、常量、数据类型。在scala中对pattern有明确的定义,在形式上有以下集中pattern:

1. 常量模式(constant patterns) 包含常量变量和常量字面量
scala> val site = "alibaba.com"
site: String = alibaba.com

scala> site match { case "alibaba.com" => println("ok") }
ok

scala> val ALIBABA = "alibaba.com"
ALIBABA: String = alibaba.com

scala> def foo(s: String) { s match {case ALIBABA => println("ok") } }
foo: (s: String)Unit

scala> foo(site)
ok
2. 变量模式(variable patterns)

注意这里如果case的模式变量名是以大写字母开头,scala会把他对待成一个常量变量,试图去找到大写字母开头的变量,否则出错。

而把要匹配的 site对象用 whateverName 变量名代替,所以它总会匹配成功。

scala> site match { case WhateverName => println(WhateverName) }
<console>:13: error: not found: value WhateverName
       site match { case WhateverName => println(WhateverName) }
                         ^
<console>:13: error: not found: value WhateverName
       site match { case WhateverName => println(WhateverName) }
                                                 ^

scala> site match { case whateverName => println(whateverName) }
alibaba.com

变量模式通常不会单独使用,而是在多种模式组合时使用,比如:

scala> List(1,2) match { case List(x,2) => println(x) }
1

里面的x就是对匹配到的第一个元素用变量x标记。

3. 通配符模式(wildcard patterns)

通配符下划线_表示一个特殊变量或占位符。

单纯的通配符模式通常在模式匹配的最后一行出现,case _ => 它可以匹配任何对象,用于处理所有其它匹配不成功的情况。

通配符模式也常和其他模式组合使用:

scala> List(1,2,3) match { case List(_, _, 3) => println("ok") }
ok

上面的 List(,,3) 里用了2个通配符表示第一个和第二个元素,这2个元素可以是任意类型。 通配符通常用于代表所不关心的部分,它不像变量模式可以后续的逻辑中使用这个变量。

4. 构造器模式(constructor patterns)
scala> :paste
// Entering paste mode (ctrl-D to finish)

trait Node
case class TreeNode(v: String, left: Node, right: Node) extends Node
case class Tree(root: TreeNode)

// Exiting paste mode, now interpreting.
// 首先定义一个二叉树
defined trait Node
defined class TreeNode
defined class Tree
// 构建一个根节点含有2个子节点的树
scala> val tree = Tree(TreeNode("root", TreeNode("left", null, null), TreeNode("right", null, null)))
tree: Tree = Tree(TreeNode(root,TreeNode(left,null,null),TreeNode(right,null,null)))

// 如果我们期望一个树的构成是根节点的左子节点值为”left”,右子节点值为”right”并且右子节点没有子节点
// 那么可以用下面的方式匹配:
scala> tree.root match {
     |   case TreeNode(_, TreeNode("left", _, _), TreeNode("right", null, null)) =>
     |     println("bingo")
     | }
bingo

使用构造器模式可以减少大量if判断代码。

5. 类型模式(type patterns)

判断对象是否是某种类型

"hello" match {case _:String => println("ok") }

需要匹配泛型的时候需要注意:

scala> :paste
// Entering paste mode (ctrl-D to finish)

def foo(a: Any) = a match {
  case a: List[String] => println("ok")
  case _ =>
}


// Exiting paste mode, now interpreting.

<console>:12: warning: non-variable type argument String in type pattern List[String] (the underlying of List[String]) is unchecked since it is eliminated by erasure
         case a: List[String] => println("ok")
                 ^
foo: (a: Any)Unit

scala> foo(List("A"))
ok

scala> foo(List(2))
ok

上面的 List[String] 里的String运行时并不能检测foo(List("A")) 和 foo(List(2)) 都可以匹配成功。

**正确的做法是: ** 通常对于泛型直接用通配符替代,上面的写为 case a : List[_] => …

6. 变量绑定模式 (variable binding patterns)

这个和前边的变量模式有什么不同?看一下代码就清楚了。 依然是上面的TreeNode,如果我们希望匹配到左边节点值为”left”就返回这个节点的话:

scala> tree.root match {
     |   case TreeNode(_, leftNode@TreeNode("left",_,_), _) => leftNode
     | }
res3: TreeNode = TreeNode(left,null,null)

用@符号绑定 leftNode变量到匹配到的左节点上,只有匹配成功才会绑定。