ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Scala之模式匹配(Patterns Matching)

Scala之模式匹配(Patterns Matching) 前言首先我们要在一开始强调一件很重要的事Scala的模式匹配发生在但绝不仅限于发生在match case语句块中这是Scala模式匹配之所以重要且有用的一个关键因素我们会在文章的后半部分详细地讨论这一点。本文原文出处: http://blog.csdn.net/bluishglc/article/details/51056230 严禁任何形式的转载否则将委托CSDN官方维护权益模式匹配的种类在Scala中一共有如下几种类型的模式匹配通配符匹配Wildcard Pattern Matching 常量匹配 Constant Pattern Matching 变量匹配Variable Pattern Matching 构造函数匹配Constructor Pattern Matching 集合类型匹配Sequence Pattern Matching 元祖类型匹配Tuple Pattern Matching 类型匹配Typed Pattern Matching 一个包罗万象的例子让我们来看一下几乎展示了所有类型的模式匹配的例子objectPatternMatchingDemo{caseclassPerson(firstName:String,lastName:String)caseclassDog(name:String)defechoWhatYouGaveMe(x:Any):Stringxmatch{// constant patternscase0zerocasetruetruecasehelloyou said hellocaseNilan empty List// sequence patternscaseList(0,_,_)a three-element list with 0 as the first elementcaseList(1,_*)a list beginning with 1, having any number of elementscaseVector(1,_*)a vector starting with 1, having any number of elements// tuplescase(a,b)sgot$aand$bcase(a,b,c)sgot$a,$b, and$c// constructor patternscasePerson(first,Alexander)sfound an Alexander, first name $firstcaseDog(Suka)found a dog named Suka// typed patternscases:Stringsyou gave me this string:$scasei:Intsthanks for the int:$icasef:Floatsthanks for the float:$fcasea:Array[Int]san array of int:${a.mkString(,)}caseas:Array[String]san array of strings:${as.mkString(,)}cased:Dogsdog:${d.name}caselist:List[_]sthanks for the List:$listcasem:Map[_,_]m.toString// the default wildcard patterncase_Unknown}defmain(args:Array[String]){// trigger the constant patternsprintln(echoWhatYouGaveMe(0))println(echoWhatYouGaveMe(true))println(echoWhatYouGaveMe(hello))println(echoWhatYouGaveMe(Nil))// trigger the sequence patternsprintln(echoWhatYouGaveMe(List(0,1,2)))println(echoWhatYouGaveMe(List(1,2)))println(echoWhatYouGaveMe(List(1,2,3)))println(echoWhatYouGaveMe(Vector(1,2,3)))// trigger the tuple patternsprintln(echoWhatYouGaveMe((1,2)))// two element tupleprintln(echoWhatYouGaveMe((1,2,3)))// three element tuple// trigger the constructor patternsprintln(echoWhatYouGaveMe(Person(Melissa,Alexander)))println(echoWhatYouGaveMe(Dog(Suka)))// trigger the typed patternsprintln(echoWhatYouGaveMe(Hello, world))println(echoWhatYouGaveMe(42))println(echoWhatYouGaveMe(42F))println(echoWhatYouGaveMe(Array(1,2,3)))println(echoWhatYouGaveMe(Array(coffee,apple pie)))println(echoWhatYouGaveMe(Dog(Fido)))println(echoWhatYouGaveMe(List(apple,banana)))println(echoWhatYouGaveMe(Map(1-Al,2-Alexander)))// trigger the wildcard patternprintln(echoWhatYouGaveMe(33d))}}相应的输入如下zero true you said hello an empty List a three-element list with 0 as the first element a list beginning with 1, having any number of elements a list beginning with 1, having any number of elements a vector starting with 1, having any number of elements got 1 and 2 got 1, 2, and 3 found an Alexander, first name Melissa found a dog named Suka you gave me this string: Hello, world thanks for the int: 42 thanks for the float: 42.0 an array of int: 1,2,3 an array of strings: coffee,apple pie dog: Fido thanks for the List: List(apple, banana) Map(1 - Al, 2 - Alexander) you gave me this string: 33d上述示例中唯一没有展示的是变量模式匹配。变量模式匹配很像通配符模式匹配唯一的区别在于使用通配符模式匹配时你不能在case推导符后面使用匹配到的值但是变量模式匹配给匹配到的值命名了一个变量名因此你可以在推导符后面使用它。下面这个例子就演示了变量模式匹配:scaladefvariableMatch(x:Any):Stringxmatch{|casei:IntsThis is an Integer:$i|caseotherValuesThis is other value:$otherValue//You can use var: otherValue.|}variableMatch:(x:Any)Stringscalaprintln(variableMatch(1))This is an Integer:1scalaprintln(variableMatch(1.0))This is other value:1.0scalaprintln(variableMatch(SSS))This is other value:SSS模式匹配的附加约束Guard上述7种模式匹配是语法层面上的模式匹配很多时候只有这7种模式匹配是不够的程序员需要根据具体的值做更细致的匹配这时我们需要对模式匹配附加更多的约束条件这些约束条件叫做Guard对应到代码上就是在case后面再添加if语句用于对匹配做更加细致的描述。让我们来看一个例子scala def testPatternGuard(x: (Int,Int)):Int x match { | case (a,a)a*2 | case (a,b)ab | } console:8: error: a is already defined as value a case (a,a)a*2 ^上述代码的设计初衷是希望通过模式匹配来判断二元元组中的两个值是不是一样如果是一样的使用一种计算逻辑如果不一样则使用另一个计算逻辑但是这段代码是不能编译通过的Scala要求**“模式必须是线性的”**也就是说模式中的变量只能出现一次。Scala restricts patterns to be linear: a pattern variable may only appear once in a pattern.在这个例子中寄希望使用一个变量让Scala在编译时帮助你判断两个值是否一值显然是做不到的所以必然会报错在这种场合就是需要使用if语句来限定匹配条件的时候了以下正确的做法:scala def testPatternGuard(x: (Int,Int)):String x match { | case (a,b) if ab sab,so, we can calc it as: a*2${a*2} | case (a,b)sa!b,just calc it as: ab${ab} | } testPatternGuard: (x: (Int, Int))String scala println(testPatternGuard((1,2))) a!b,just calc it as: ab3 scala println(testPatternGuard((1,2))) a!b,just calc it as: ab3Sealed Classe与模式匹配如果一个类被声明为sealed则除了在定义这个class的文件内你可以创建它的子类之外其他任何地方都不允许一个类去继承这个类。在进行模式匹配时我们需要时刻留心你的case语句是否能cover所有可能的情形但如果在匹配一个类族特别是子类时可能会出现无法控制的情况因为如果类族是可以自由向下派生的话过去覆盖了各种情形的case语句就可能不再“全面”了。所以使用sealed class是对模式匹配一种保护。另外使用sealed class还可以从编译器那边得到一些额外的好处当你试图针对case继承自sealed class的case类进行模式匹配时如果漏掉了某个某些case类编译器在编译时会给一个warning. 所以说当你想为一模式匹配而创建一个类族时或者说你的类族将要被广发使用于模式匹配时你最好考虑将你的类族超类限定为sealed。比如当你定义这样一组sealed classes时sealedabstractclassExprcaseclassVar(name:String)extendsExprcaseclassNumber(num:Double)extendsExprcaseclassUnOp(operator:String,arg:Expr)extendsExprcaseclassBinOp(operator:String,left:Expr,right:Expr)extendsExpr如果你写了这样一个模式匹配scala def describe(e: Expr): String e match { | case Number(_) a number | case Var(_) a variable | } console:12: warning: match may not be exhaustive. It would fail on the following inputs: BinOp(_, _, _), UnOp(_, _) def describe(e: Expr): String e match { ^ describe: (e: Expr)String编译器就会给你一个warnining。模式匹配无处不在上面我们演示的所有模式匹配都是基于match case语句块的诚如我们在文章一开始就强调的如果模式匹配仅仅存在于match case语句中那这项优秀特性的辐射的能量将会大打折扣Scala正是将模式匹配发扬到编程的方方面面才使得模式匹配在Scala里真正地大放异彩。变量定义中的模式匹配这可能是Scala的模式匹配最吸引人的地方了在Scala里每当你定义一个变量时你可以直接利用模式匹配同时为多个变量一次性赋值这一特性被广泛使用于从元组Case类和构造器中提取对应的值赋给多个变量。以下展示了几种常见的示例从元组中提取变量scala val (number,string)(1,a) number: Int 1 string: String a scala println(snumber$number) number1 scala println(sstring$string) stringa从构造器中提取额变量scala case class Person(name:String,age:Int) defined class Person scala val Person(name,age)Person(John,30) name: String John age: Int 30 scala println(sname$name) nameJohn scala println(sage$age) age30一个更常见的例子是在main函数中提取命令行传递过来的参数列表defmain(args:Array[String]){valArray(arg1,agr2)args.....}case语句块函数字面量中的模式匹配在Scala之偏函数Partial Function 一文中我们详细介绍了偏函数其中提到使用不含match的case语句块可以构建一个偏函数的字面量这个偏函数具有多个“入口”每一个入口都由一个case描述这样在调用一个偏函数时根据传入的参数会匹配到一个case这个过程也是模式匹配这种模式匹配和match case 的模式匹配是很相似的。for循环中的模式匹配如果我们认为for循环中声明的局部迭代变量就是一个普通变量那么在for循环中使用的模式匹配实质上就是前面提到的变量定义中使用的模式匹配来看一个列子scala val capitals Map(France - Paris, Japan - Tokyo) capitals: scala.collection.immutable.Map[String,String] Map(France - Paris, Japan - Tokyo) scala for ((country, city) - capitals) | println(The capital of country is city) The capital of France is Paris The capital of Japan is Tokyo更深的理解为什么我们需要“模式匹配”在一次对Martin Odersky的采访中Martin Odersky这样解释到我们每个人都有复杂的数据。如果我们坚持严格的面向对象的风格那么我们并不希望直接访问数据内部的树状结构。相反我们希望调用 方法然后在方法中访问。如果我们能够这样做那么我们就再也不需要模式匹配了因为这些方法已经提供了我们需要的功能。但很多情况下对象并不提供我们需要的方法而且我们无法或者不愿向这些对象添加方法。… 从本质上讲当你从外部取得具有结构的对象图时模式匹配就必不可少。你会在若干情况下遇到这种现象。在这面这段论述中以及Martin Odersky举例讲到的从XML构建一个DOM类型结构都无不让我联想到我之前写过的文章一段关于多态的沉思 在这篇文章里我所思索的问题其实正是应用模式匹配的绝佳场景将模式匹配应用于提取一堆值这么好用为什么不用呢就像某种反向的表达式。正向的表达式向结果中插入值反向的表达式却是给定结果一旦匹配成功就能反过来从结果中抽取出一大堆值。
返回列表