Scala 实战之案例类和模式匹配--模式的种类
in Scala with 0 comment

Scala 实战之案例类和模式匹配--模式的种类

in Scala with 0 comment

一、介绍

为了使用交互式 Scala 解释器,你可以在打开的终端中输入命令:

su -l hadoop
scala

当出现scala>开始的命令行提示符时,就说明你已经成功进入解释器了。如下图所示。

image-1655708277836

本实验的所有命令及语句均可在 Shell 中输入。

二、开发准备

三、步骤

3.1 通配模式

通配符 “_” 可以用来匹配任意对象。通常在模式匹配中作为最后一个匹配项,匹配其它所有的输入对象。比如:

abstract class Expr
case class Var(name:String) extends Expr
case class Number(num:Double) extends Expr
case class BinOp(operator:String,left:Expr,right:Expr) extends Expr


def simplifyBinOp(expr :Expr) = expr match {
    case BinOp(op,left,right) => println( expr + " is a binary operation")
    case _ =>
}

image-1655708287305

通配模式也可以用来忽略一些你不打算处理的对象,比如在本例中,只关心输入是否为一个二元操作,其它的直接忽略。如果你不关心具体的操作符,如左右操作符,可以直接使用通配符忽略这些部分,如:

def simplifyBinOp(expr :Expr) = expr match {
    case BinOp(_,_,_) => println( expr + " is a binary operation")
    case _ =>
}

image-1655708295958

3.2 常量模式

常量模式可以匹配和常量值本身相同的输入。任意的字面量都可以作为常量模式。此外,所有的单例(singleton)也可以作为常量模式。比如 Nil 可以匹配空列表。

def describe(x:Any) =x match {
    case 5 => "five"
    case true => "truth"
    case "hello" => "hi!"
    case Nil => "the empty list"
    case _ => "something else"
}

image-1655708305484

本例定义了多个常量模式,我们来看看一些测试结果:

scala> describe (5)
res0: String = five

scala> describe(true)
res1: String = truth

scala> describe ("hello")
res2: String = hi!

scala> describe(Nil)
res3: String = the empty list

scala> describe(List(1,3,4))
res4: String = something else

3.3 变量模式

一个变量模式可以匹配任意对象,在这一点上和通配符 “_” 一样。但和通配符不同的是, Scala 将这个变量绑定到输入的对象上,然后你在后面定义的表示中可以引用这个变量。比如下面代码匹配 0 ,对于其它的对象使用了变量模式,这其后的表示中可以引用这个变量:

def isZero(x:Any) = x match{
    case 0 => "zero"
    case somethingElse => "not zero:" + somethingElse
}

image-1655708314947

scala> isZero(0)
res5: String = zero

scala> isZero(1)
res6: String = not zero:1

3.4 构造器模式

构造器模式功能非常强大,比如一个构造器模式可以定义为 BinOp(“+”,e,Number(0)) 。 它由一个名称 BinOp (某个 case class 的名称)和一系列由括号分开的模式构成( “+” , eNumber(0) )。这个模式首先检查输入对象是否是 BinOp 类型的对象,然后检查构造器参数是否匹配输入的对象。

这些额外的模式表示 Scala 支持深度匹配,这些模式不仅仅检查最高层次的匹配,并且检查其内部内容的匹配,同时这些额外的模式自身还可以是构造器模式,因此你可以构造嵌套任意层次的构造器模式。比如:

def simplifyBinOp(expr :Expr) = expr match {
    case BinOp("+",e,Number(0)) => println(" a deep match")
    case _ =>
}

image-1655708324165

它的第三个参数 Number(0) 自身也是一个构造器模式,其参数又匹配 0 ,因此有三个层次的匹配。

3.5 序列模式

你也可以匹配如 List 和数组等序列类型的数据。和匹配 case class 语法类似,但你可以指明序列中任意数量的元素。比如下面这个例子,匹配了含有三个元素且其中首元素为 0 的列表:

List(0,2,4) match{
      case List(0,_,_) => print ("found it " )
      case _ =>
      }

image-1655708336597

如果你需要匹配一个未指明长度的序列,可以使用 “_*” 作为模式的后一元素,这个 “_*” 可以匹配任意数目的元素(包括 0 个元素)。

def simplifyList(expr :Expr) = expr match {
      case List(0,_*) => print ("found it")
      case _ =>
      }

image-1655708346492

3.6 多元组模式

除序列外,你可以匹配多元组,比如:

def tupleDemo(expr:Any) =
     expr match{
       case (a,b,c) => print("matched " + a +":" +b +":"+c )
       case _ =>
     }


scala> tupleDemo(2,3,4)
matched 2:3:4

image-1655708376084

进行测试:

image-1655708387604

3.7 类型模式

你可以使用类型模式匹配来代替类型检查和类型转换。比如:

def generalSize(x:Any) = x match{
    case s:String => s.length
    case m:Map[_,_] => m.size
    case _ => -1
}

image-1655708413133

函数 generalSize 返回某些对象长度或是大小。它的参数类型为Any,因此可以传入任意类型的数据。 模式s:String 是一个类型模式,用于匹配任意类型为String的非空对象。变量s为匹配的字符串。

第二个匹配 m:Map[_,_] 用于匹配任意类型的 Map 对象。这里我们不关心具体的 keyvalue ,因此使用通配符 _ 。如果你需要在后面表达式中使用 keyvalue ,可以使用 key,value 替换掉 _

对于 Map 类型的数据,我们能否匹配指定 keyvalue 类型的数据呢,比如:

def  isIntIntMap(x:Any) = x match {
    case m:Map[Int,Int]=>true
    case _ => false
}

此时编译器会给出警告:

<console>:9: warning: non-variable type argument Int in type pattern Map[Int,Int] is unchecked since it is eliminated by erasure
       case m:Map[Int,Int]=>true
              ^
isIntIntMap: (x: Any)Boolean

Scala 和 Java 类似,对于 generic 类采用了“type erasure”,也就是说,在运行时不保存 Map 的 Keyvalue 的类型,因此我们无法匹配指定类型 keyvalue 的 Map 对象。

尝试输入下面的语句:

scala> isIntIntMap(Map(1->1))
res14: Boolean = true

scala> isIntIntMap(Map("a"->"b"))
res15: Boolean = true

可以看到,这两个都返回 true,这和预期不同。因此,对于这种情况,编译器会给出警告,pattern Map[Int,Int] 中的类型不起作用。

但有一个特例,数组和一般的 generic 处理不同,它支持匹配元素类型。比如:

def isStringArray(x:Any) = x match{
    case a:Array[String]=>"yes"
    case _ => "no"
}

image-1655708426515

在 Shell 中输入下方的语句测试一下:

scala> val as =Array("abc")
as: Array[String] = Array(abc)

scala> isStringArray(as)
res16: String = yes

scala> val ai = Array(1,2,3)
ai: Array[Int] = Array(1, 2, 3)

scala> isStringArray(ai)
res17: String = no

3.8 变量绑定

除了独立的变量模式外,你还可以把一个变量添加到另外的模式中。可以简单地定义一个变量,然后再添加一个 @ 符号,然后再写其它的模式。这就定义了一个变量绑定过的模式。这意味着还是按照标准的模式匹配来匹配输入的对象,如果匹配成功,匹配成功的对象会赋值到定义的变量中。

例如:

expr match {
    case UnOp("abs",e @ UnOp("abs",_)) => e
    case _ =>
}

这里定义了一个绑定到变量的模式, e @ UnOp(“abs”,_) ,而整个模式是匹配运用了两次 “abs” 操作的对象。如果匹配成功,e 将被赋值为匹配 UnOp(“abs”,_) 的部分。比如:

scala> case class UnOp(operator:String, arg:Expr) extends Expr
defined class UnOp

scala>  val expr = UnOp("abs",UnOp("abs",Number(5)))
expr: UnOp = UnOp(abs,UnOp(abs,Number(5.0)))

scala> expr match {
     |     case UnOp("abs",e @ UnOp("abs",_)) => e
     |     case _ =>
     |   }
res0: Any = UnOp(abs,Number(5.0))

可以看到,匹配 UnOp(“abs”,UnOp(“abs”,Number(5)))成功后,e 赋值为 UnOp(abs,Number(5.0))

四、总结

本节我们详细地展开讲解了各种模式并对其进行了学习。至此,你应该思考如何将不同的模式应用在不同的问题上。而有关这些模式的更多详细说明,可以参考 Scala 的官方手册