当前位置 : 主页 > 手机开发 > harmonyos >

Scala——模式匹配和样例类

来源:互联网 收集:自由互联 发布时间:2023-10-08
Scala强大的模式匹配机制,可以应用在switch语句、类型检查以及“析构”等场合。样例类对模式匹配进行了优化。 更好的switch var sign = . . .val ch : Char = . . . ch match { case '+' = sign = 1 case

Scala强大的模式匹配机制,可以应用在switch语句、类型检查以及“析构”等场合。样例类对模式匹配进行了优化。

更好的switch

 

var         sign         =         .         .         .


val         ch         :         Char         =         .         .         .


                  


ch         match         {


           case         '+'         =         >         sign         =         1


           case         '-'         =         >         sign         =         -         1


           case         _         =         >         sign         =         0


}



上面代码中,case _模式对应于switch语句中的default,能够捕获剩余的情况。如果没有模式能匹配,会抛出MatchError。而且不像常见的switch语句,在一种模式匹配之后,需要使用break来声明分支不会进入下一个分支。

match是表达式,不是语句,所以是有返回值的,故可将代码简化:


sign         =         ch         match         {


           case         '+'         =         >         1


           case         '-'         =         >         -         1


           case         _         =         >         0


}




match表达式中可以使用任何类型。模式总是从上往下进行匹配。

守卫

看代码就好,与if表达式的守卫相同作用:

ch         match         {


           case         '+'         =         >         sign         =         1


           case         '-'         =         >         sign         =         -         1


           case         _         if         Character         .         isDigit         (         ch         )         =         >         digit         =         Character         .         digit         (         ch         ,         10         )


           case         _         =         >         sign         =         0


}




 

模式中的变量

如果在case关键字后跟着一个变量名,那么匹配的表达式会被赋值给那个变量。case _是这个特性的一个特殊情况,变量名是_。


"Hello, world"         foreach         {         c         =         >         println         (


             c         match         {


               case         ' '         =         >         "space"


               case         ch         =         >         "Char: "         +         ch


             }


)         }




经过我的尝试,在如果变量名是_,那么在=>后使用_是不行的。

在模式中使用变量可能会与常量冲突。


import         scala         .         math         .         _


x         match         {


           case         Pi         =         >         .         .         .


           .         .         .


}



在上面的代码中,要如何判断Pi这个标志符是一个用来匹配的常量还是模式中的变量?规则是:变量比需要以小写字母开始。如果有常量是小写字母开头的,那么需要用反引号将常量名包起来:


import         java         .         io         .         File         .         _


str         match         {


           case         `         pathSeparator         `         =         >         .         .         .


           .         .         .


}




 

类型模式

相比使用isInstanceOf来判断类型,使用模式匹配更好。


obj         match         {


           case         x         :         Int         =         >         x


           case         s         :         String         =         >         Integer         .         parseInt         (         s         )


           case         _         :         BigInt         =         >         Int         .         MaxValue


           case         _         =         >         0


}




在匹配类型时,需要使用一个变量名,否则就是使用对象本身来进行匹配了。


obj         match         {


           case         _         :         BigInt         =         >         Int         .         MaxValue                    // 匹配任何类型为BigInt的对象


           case         BigInt         =         >         -         1                    // 匹配类型为Class的BigInt对象


}



因为匹配是发生在运行期的,而且JVM中泛型的类型信息会被擦掉,因此不能使用类型来匹配特定的Map类型(大部分集合类型也都不可以吧):


case         m         :         Map         [         String         ,         Int         ]         =         >         .         .         .                    // 不行


case         m         :         Map         [         _         ,         _         ]         =         >         .         .         .                    // 匹配通用的Map,OK

 




但对于数组来说,类型信息是完好的,所以可以在Array上匹配。

匹配数组、列表和元组

 

arr         match         {


           case         Array         (         0         )         =         >         "0"                    // 匹配包含0的数组


           case         Array         (         x         ,         y         )         =         >         x         +         " " +         y                    // 匹配任何带有两个元素的数组,并将元素绑定到x和y


           case         Array         (         0         ,         _         *         )         =         >         "0 ..."                    // 匹配任何以0开始的数组


           case         _         =         >         "something else"


}




下面的模式匹配,功能与上面的代码是一样的,不过将数组换成了列表。


lst         match         {


           case         0         ::         Nil         =         >         "0"


           case         x         ::         y         ::         Nil         =         >         x         +         " "         +         y


           case         0         ::         tail         =         >         "0 ..."


           case         _         =         >         "something else"


}



与上面两个例子差不多,模式匹配也可以使用在元组上。

注意到变量将会被绑定到这三种数据结构的不同部分上,这种操作被称为“析构”。

提取器

在上一节中,使用模式匹配来对数组、列表和元组进行了匹配,在这个过程的背后的是提取器(extractor)机制。使用unapply来提取固定数量的对象,使用unapplySeq来提取一个序列。

在前面的代码 case Array(0, x) => ...中, Array(0, x)部分实际上是使用了伴生对象中的提取器,实际调用形式是: Array.unapplySeq(arr)。根据Doc,提取器方法接受一个Array参数,返回一个Option。

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

val         pattern         =         "([0-9]+) ([a-z]+)"         .         r


"99 bottles"         match         {


           case         pattern         (         num         ,         item         )         =         >         .         .         .


}



 

变量声明中的模式

在变量声明中的模式对于返回对偶(更广一点也可以用在元组上吧?)的函数来说很有用。

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


val         (         q         ,         r         )         =         BigInt         (         10         )         /         %         3                    // 返回商和余数的对偶


val         Array         (         first         ,         second         ,         _         *         )         =         arr                    // 将第一和第二个分别给first和second



 

for表达式中的模式

这一部分的内容多在介绍for表达式时提过了,不过当时并没有意识到使用的是模式。


import         scala         .         collection         .         JavaConversions         .         propertiesAsScalaMap


                  


for         (         (         k         ,         v         )         <         -         System         .         getProperties         (         )         )                    // 这里使用了模式


           println         (         k         +         " -> "         +         v         )


                  


for         (         (         k         ,         ""         )         <         -         System         .         getProperties         (         )         )                    // 失败的匹配会被忽略,所以只打印出值为空的键


           println         (         k         )




 

样例类

样例类是种特殊的类,经过优化以用于模式匹配。


abstract         class         Amount


// 继承了普通类的两个样例类


case         class         Dollar         (         value         :         Double         )         extends         Amount


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


                  


// 样例对象


case         object         Nothing          extends         Amount



使用:

amt         match         {


           case         Dollar         (         v         )         =         >         "$"         +         v


           case         Currency         (         _         ,         u         )         =         >         "Oh noes, I got "         +         u


           case         Nothing         =         >         ""                    // 样例对象没有()


}

 




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

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

除了上述之外,样例类和其他类型完全一样,方法字段等。

copy方法和带名参数

样例类的copy方法创建一个与现有对象相同的新对象。可以使用带名参数来修改某些属性:


val         amt         =         Currency         (         29.95         ,         "EUR"         )


val         price         =         amt         .         copy         (         values         =         19.95         )


val         price         =         amt         .         copy         (         unit         =         "CHF"         )

 




 

case语句中的中置表示法

如果unapply方法产出一个对偶,则可以在case语句中使用中置表示法。对于有两个参数的样例类,可以使用中置表示法。



 

amt         match         {         case         a         Currency         u         =         >         .         .         .         }                    // 等于case Currency(a, u)




这个特性的本意是要匹配序列。举例,List对象要么是Nil,要么是样例类::。所以可以:


lst         match         {         case         h         ::         t         =         >         .         .         .         }                    // 等同于case ::(h, t),调用::.unapply(lst)

 




多个中置表达式放在一起时会比普通的形式更加易读。

匹配嵌套结构

这个解释起来有点绕。


abstarct          class         Item


case         class         Article         (         description         :         String         ,         price         :         Double         )         extends         Item


case         class         Bundle         (         description         :         String         ,         price         :         Double         ,         items         :         Item         *         )         extends         Item


                  


Bundle         (         "Father's day special"         ,         20.0         ,


           Article         (         "Scala for the Impatient"         ,         39.95         )         ,


           Bundle         (         "Anchor Distillery Sampler"         ,         10.0         ,


             Article         (         "Old Potrero Straight Rye Whisky"         ,         79.95         )         ,


             Article         (         "Junipero Gin"         ,         32.95         )


           )


)



模式可以匹配到特定的嵌套:


case         Bundle         (         _         ,         _         ,         Article         (         descr         ,         _         )         ,         _         *         )         =         >         .         .         .

 


上面的代码中descr这个变量被绑定到第一个Article的description。另外还可以使用@来将值绑定到变量:


// art被绑定为第一个Article,rest是剩余的Item序列


case         Bundle         (         _         ,         _         ,         art         @         Article         (         _         ,         _         )         ,         rest         @         _         *         )         =         >         .         .         .

 




下面是个使用了模式匹配来递归计算Item价格的函数。



实际应用

def         price         (         it         :         Item         )         :         Double         =         it         match         {


           case         Article         (         _         ,         p         )         =         >         p


           case         Bundle         (         _         ,         disc         ,         its         @         _         *         )         =         >         its         .         map         (         price         _         )         .         sum         -         disc


}



 

密封类

当使用样例类来做模式匹配时,如果要让编译器确保已经列出所有可能的选择,可以将样例类的通用超类声明为sealed。

密封类的所有子类都必须在与该密封类相同的文件中定义。

如果某个类是密封的,那么在编译期所有的子类是可知的,因而可以检查模式语句的完整性。

让所有同一组的样例类都扩展某个密封的类或特质是个好的做法。

模拟枚举

可以使用样例类来模拟枚举类型:


sealed         abstract         class         TrafficLightColor


case         object         Red         extends         TrafficLightColor


case         object         Yellow         extends         TrafficLightColor


case         object         Green         extends         TrafficLightColor


                  


color         match         {


           case         Red         =         >         "stop"


           case         Yellow         =         >         "hurry up"


           case         Green         =         >         "go"


}




 

Option类型

Option类型用来表示可能存在也可能不存在的值。样例子类Some包装了某个值,而样例对象None表示没有值。Option支持泛型。


scores         .         get         (         "Alice"         )         match         {


           case         Some         (         score         )         =         >         println         (         score         )


           case         Nome         =         >         println         (         "No score"         )


}




 

偏函数(L2)

被包在花括号内的一组case语句是一个偏函数。

偏函数是一个并非对所有输入值都有定义的函数,是PartialFunction[A, B]类的一个实例,其中A是参数类型,B是返回类型。该类有两个方法:apply方法从匹配的模式计算函数值;isDefinedAt方法在输入至少匹配其中一个模式时返回true。


val         f         :         PartialFunction         [         Char         ,         Int         ]         =         {         case         '+'         =         >         1         ;         case         '-'         =         >         -         1         }


f         (         '-'         )                    // 返回-1


f         .         isDefinedAt         (         '0'         )                    // false


f         (         '0'         )                    //抛出MatchError



 


网友评论