前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Scala学习笔记(八)

Scala学习笔记(八)

作者头像
fengzhizi715
发布2018-08-24 17:11:02
1K0
发布2018-08-24 17:11:02
举报

1. 模式匹配简介

模式匹配是 Scala 的重要特性之一,前面两篇笔记Scala学习笔记(六) Scala的偏函数和偏应用函数Scala学习笔记(七) Sealed Class 和 Enumeration都是为了这一篇而铺垫准备的。

在jdk1.7之前,Java的 switch 关键字只可以处理原生类型(int 、short 、byte 、char)和枚举类型。在jdk1.7以后,switch新增了对String类型的处理。Scala 虽然没有switch关键词,但是它的模式匹配可以看做是 switch 的加强版,能够处理更加复杂的类型和场景。

先来看一个简单的例子。

代码语言:javascript
复制
scala> def judgeGrade(name:String,grade:String) {
     |   grade match {
     |     case "A" => println(name+", you are excellecnt")
     |     case "B" => println(name+", you are good")
     |     case "C" => println(name+", you are just so so")
     |     case _ if name == "Tony" => println(name+", you are a good boy,come on")
     |     case _ => println("you need to work harder")
     |   }
     | }
judgeGrade: (name: String, grade: String)Unit

scala> judgeGrade("Monica","A")
Monica, you are excellecnt

scala> judgeGrade("Lily","B")
Lily, you are good

scala> judgeGrade("Tom","C")
Tom, you are just so so

scala> judgeGrade("Tony","D")
Tony, you are a good boy,come on

scala> judgeGrade("Jacky","D")
you need to work harder

通过这个例子,可以看到模式匹配的语法大致是这样的。

代码语言:javascript
复制
变量 match { 
  case 值1 => 代码
  case 值2 => 代码
  ...
  case 值N if (...) => 代码
  case _ => 代码
}

注意,case后面的值1到值N,可以是相同类型也可以是不同类型的。

if (...) 是守卫条件,后面的例子会看到。

在最后一行指令中_是一个通配符,它保证了我们可以处理所有的情况。否则当传进一个不能被匹配的值的时候,你将获得一个运行时错误。

2. 模式匹配类型

Scala的模式匹配可以支持常量模式、变量模式、序列模式、元组模式、变量绑定模式等等。

2.1常量匹配

case 后面的值是常量。

代码语言:javascript
复制
scala> def matchConstant(x:Any) = x match {
     |   case 1 => "One"
     |   case "two" => "Two"
     |   case "3" => "Three"
     |   case true => "True"
     |   case null => "null value"
     |   case Nil => "empty list"
     |   case _ => "other value"
     | }
matchConstant: (x: Any)String

scala> println(matchConstant(1))
One

scala> println(matchConstant(true))
True

scala> println(matchConstant(null))
null value

scala> println(matchConstant(List()))  //匹配到Nil
empty list

scala> println(matchConstant(false))
other value

特别需要注意的是,Nil是一个空的List,定义为ListNothing。iOS开发者会比较熟悉Nil,但是这里的Nil跟OC中的Nil是两个完全不同的概念。

2.2 变量匹配

case 后面的值是变量

代码语言:javascript
复制
scala> def matchVariable(x:Any) = x match {
     |    case x if(x==1) => x
     |    case x if(x=="Tony") => x
     |    case x:String => "other value:" + x
     |    case _ => "unexpected value:"+x
     | }
matchVariable: (x: Any)Any

scala> println(matchVariable(1))
1

scala> println(matchVariable("Tony"))
Tony

scala> println(matchVariable("Scala"))
other value:Scala

scala> println(matchVariable(2))
unexpected value:2

2.3 序列匹配

case 后面的值是数组、List、Range等集合。

代码语言:javascript
复制
scala> def matchSeq(x:Any) = x match {
     |    case List("Tony",_,_*) => "Tony is in the list"
     |    case List(_,second,_*) => "The second is:"+second
     |    case Array(first,second,_*) => "first:"+first+",second:"+second
     |    case _ => "Other seq"
     | }
matchSeq: (x: Any)String

scala> val list1 = List("Tony","Cafei","Aaron")
list1: List[String] = List(Tony, Cafei, Aaron)

scala> val list2 = "android"::"iOS"::"H5"::Nil
list2: List[String] = List(android, iOS, H5)

scala> val array1 = Array("Hadoop","Spark","ES")
array1: Array[String] = Array(Hadoop, Spark, ES)

scala> val array2 = Array("Scala")
array2: Array[String] = Array(Scala)

scala> println(matchSeq(list1))
Tony is in the list

scala> println(matchSeq(list2))
The second is:iOS

scala> println(matchSeq(array1))
first:Hadoop,second:Spark

scala> println(matchSeq(array2))
Other seq

需要注意的是,

代码语言:javascript
复制
val list2 = "android"::"iOS"::"H5"::Nil

看上去很奇怪,其实等价于

代码语言:javascript
复制
val list2 = List("android","iOS","H5")

list分为head和tail两个部分,head是list的第一个元素,tail是list中除了head外的其余元素组成的list。用::连接list时,尾节点要声明成Nil。

所以呢,在case后面可以使用::的形式,例如:

代码语言:javascript
复制
scala> def matchSeq2(x:Any) = x match {
     |   case x::y::Nil => x+" "+y
     |   case _ => "Something else"
     | }
matchSeq2: (x: Any)String

scala> val list3 = List(1,2)
list3: List[Int] = List(1, 2)

scala> println(matchSeq2(list2))
Something else

scala> println(matchSeq2(list3))
1 2 

2.4 元组匹配

case 后面的值是元组类型。

代码语言:javascript
复制
scala> def matchTuple(x:Any) = x match {
     |    case (first,_,_) => first
     |    case _ => "Something else"
     | }
matchTuple: (x: Any)Any

scala> val t = ("Tony","Cafei","Aaron")
t: (String, String, String) = (Tony,Cafei,Aaron)

scala> println(matchTuple(t))
Tony

值得注意的是,在元组模式中不能使用_*来匹配剩余的元素,_*只适用于序列模式。

2.5 类型匹配

它可以匹配输入待匹配变量的类型。

代码语言:javascript
复制
scala> def matchType(x:Any) = x match {
     |   case s:String => "the string length is:"+s.length
     |   case m:Map[_,_] => "the map size is:"+m.size
     |   case _:Int | _:Double => "the number is:"+x
     |   case _ => "unexpected value:"+x
     | }
matchType: (x: Any)String

scala> println(matchType("test"))
the string length is:4

scala> println(matchType(1))
the number is:1

scala> println(matchType(1.0d))
the number is:1.0

scala> println(matchType(true))
unexpected value:true

scala> val map = Map("one"->1,"two"->2,"three"->3)
map: scala.collection.immutable.Map[String,Int] = Map(one -> 1, two -> 2, three -> 3)

scala> println(matchType(map))
the map size is:3

在这里,case 子句支持"或"逻辑,使用|即可。

如果上述代码使用Java来改写的话,需要不断地使用instanceof来做判断类型。

类型擦除(Type erasure)

上面的类型模式示例中的Map部分,其实只是匹配了该变量是否为Map类型,并没有匹配其中的key和value的类型。如果同时需要匹配精确的key和value的类型的话,例如下面代码中匹配key和value都是Int类型的Map,会提示警告。

代码语言:javascript
复制
scala> def isIntIntMap(x: Any) = x match {
     |   case m: Map[Int, Int] => true
     |   case _ => false
     | }
<console>:12: warning: non-variable type argument Int in type pattern scala.collection.immutable.Map[Int,Int] (the underlying of Map[Int,Int]) is unchecked since it is eliminated by erasure
         case m: Map[Int, Int] => true
                 ^
isIntIntMap: (x: Any)Boolean

由于Scala 使用了泛型的类型擦除模式,代码在运行时会将类型参数忽略掉。所以上面的代码在运行时并不能去判断当前Map对象的key和value类型是否为Int或其他类型。

代码语言:javascript
复制
scala> isIntIntMap(Map(1->1))
res10: Boolean = true

scala> isIntIntMap(Map("string"->"value"))
res11: Boolean = true

但是Array不会类型擦除,可以指定Array对象中元素的类型。

2.6 变量绑定匹配

可以将匹配的对象绑定到变量上。首先写一个变量名,然后写一个@符号,最后写入该匹配的对象。如果匹配成功,则将变量设置为匹配的对象。

代码语言:javascript
复制
scala> case class Person(name: String, age: Int)
defined class Person

scala> val person = Person("Tony",18)
person: Person = Person(Tony,18)

scala> person match {
     |   case p @Person(_,age) => println(s"${p.name},age is $age")
     |   case _ => println("Not a person")
     | }
Tony,age is 18

3. 模式匹配和Case Class

Case Class在Scala学习笔记(四) 类的初步中有提到。

3.1构造器模式匹配

case 后面的值是类构造器。

代码语言:javascript
复制
scala> case class Person(name:String,age:Int)
defined class Person

scala> val tony = Person("Tony",18)
tony: Person = Person(Tony,18)

scala> val monica = Person("Monica",15)
monica: Person = Person(Monica,15)

scala> val tom = Person("Tom",20)
tom: Person = Person(Tom,20)

scala> def matchConstructor(x:Any) = x match {
     |    case Person("Tony",18) => println("Hi Tony")
     |    case Person("Monica",15)=> println("Hi Monica")
     |    case Person(name,age) => println(s"Who are you,$age year-old person named $name?")
     | }
matchConstructor: (x: Any)Unit

scala> matchConstructor(tony)
Hi Tony

scala> matchConstructor(monica)
Hi Monica

scala> matchConstructor(tom)
Who are you,20 year-old person named Tom?

如果在类中声明了与该类相同的名字的 object 则该object 是该类的“伴生对象”。伴生对象有一个apply()用于构造对象,跟apply()对偶的是unapply()用于提取和“解构”。上面例子的匹配,就是用了Person.unapply(...)。

Person类是case class,创建时就帮我们实现了一个伴生对象,这个伴生对象里定义了apply()和unapply()。

3.2 Sealed Class的模式匹配

使用Sealed Class能保证所有的匹配情况都列举出来。

其实,在Scala学习笔记(七) Sealed Class 和 Enumeration中,已经提到了Sealed Class的模式匹配

4.模式匹配的其他用法

模式匹配并不仅仅局限于case语句。在定义变量时,也可以使用模式匹配。

例如:

代码语言:javascript
复制
scala> val (x,y) = (1,2)
x: Int = 1
y: Int = 2

4.1 for循环中使用

foreach方法

代码语言:javascript
复制
scala> for (i<-List("Java","Scala","Kotlin","Groovy"))
     |    println(i)
Java
Scala
Kotlin
Groovy

变量绑定,相当于给Scala设置别名index

代码语言:javascript
复制
scala> for(index@"Scala" <- List("Java","Scala","Kotlin","Groovy"))
     |   println(index)
Scala

条件表达格式

代码语言:javascript
复制
scala> for((language,"Hadoop") <- Set("Scala" -> "Spark","Java" -> "Hadoop")){
     |   println(language)
     | }
Java

4.2 正则表达式中使用

代码语言:javascript
复制
scala> val pattern="(S|s)cala".r
pattern: scala.util.matching.Regex = (S|s)cala

scala> val str="Scala is scalable and cool language"
str: String = Scala is scalable and cool language

scala> println(pattern findFirstIn str)
Some(Scala)

scala> println((pattern findAllIn str).mkString(", "))
Scala, scala

scala> println(pattern replaceFirstIn(str, "Java"))
Java is scalable and cool language

Scala 的正则表达式就是提取器,Scala会把每个括号里的匹配都展开到一个模式变量里。比如"(S|s)cala".r有一个unapply()方法,它返回OptionString。另一方面"(S|s)(cala)".r的unapply会返回OptionString,String。

代码语言:javascript
复制
scala> val numitemPattern="""([0-9]+) ([a-z]+)""".r
numitemPattern: scala.util.matching.Regex = ([0-9]+) ([a-z]+)

scala> val line="9527 scala"
line: String = 9527 scala

scala> line match{
     |       case numitemPattern(num,blog)=> println(num+"\t"+blog)
     |       case _=>println("hahaha...")
     |     }
9527    scala

4.3 异常处理中使用

Scala 抛出异常的语法和 Java 中的抛出异常语法是一致的。

但是Scala 的try...catch语句和 Java 的有些不一样,catch语句中通过case语句来捕获对应的异常。

代码语言:javascript
复制
catch {
  case e: IllegalArgumentException => println("illegal arg. exception");
  case e: IllegalStateException    => println("illegal state exception");
  case e: IOException              => println("IO exception");
}

再结合一下final语句。

代码语言:javascript
复制
try {
  throwsException();
} catch {
  case e: IllegalArgumentException => println("illegal arg. exception");
  case e: IllegalStateException    => println("illegal state exception");
  case e: IOException              => println("IO exception");
} finally {
  println("this code is always executed");
}

4.4 Option类中使用

Scala 语言中包含一个标准类型 Option 类型,代表可选值。Option 类型的值有两个可能的值,一个为 Some(x) 其中 x 为有效值,另外一个为 None 对象,代表空值。

代码语言:javascript
复制
scala> val books=Map("hadoop"->5,"spark"->6,"hbase"->7)
books: scala.collection.immutable.Map[String,Int] = Map(hadoop -> 5, spark -> 6, hbase -> 7)

scala> books.get("hadoop")
res0: Option[Int] = Some(5)

scala> books.get("hive")
res1: Option[Int] = None

scala> books.get("hive").getOrElse("No such book") // 不存在的元素则使用其默认的值
res2: Any = No such book

将 Option 类型的值放开,使用模式匹配:

代码语言:javascript
复制
scala> def matchOption(x:Option[Int]) = x match {
     |    case Some(s) => s
     |    case None => "?"
     | }
matchOption: (x: Option[Int])Any

scala> matchOption(books.get("hadoop"))
res3: Any = 5

scala> matchOption(books.get("hive"))
res4: Any = ?

scala> 

OptionT实际上就是一个容器,可以把它看做是一个集合,只不过这个集合中要么只包含一个元素(被包装在Some中返回),要么就不存在元素(返回None)。既然是一个集合,那么可以对它使用map、foreach或者filter等方法。

总结

模式匹配是 Scala 区别于 Java 的重要特征。我们看到了模式匹配的各种用法,在实际开发中模式匹配也应用于各个方面。

先前的文章:

Scala学习笔记(七) Sealed Class 和 Enumeration

Scala学习笔记(六) Scala的偏函数和偏应用函数

Scala学习笔记(五) 抽象类以及类中的一些语法糖

Scala学习笔记(四) 类的初步

Scala学习笔记(三)

Scala学习笔记(二)

Scala学习笔记(一)

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.05.15 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 模式匹配简介
  • 2. 模式匹配类型
    • 2.1常量匹配
      • 2.2 变量匹配
        • 2.3 序列匹配
          • 2.4 元组匹配
            • 2.5 类型匹配
              • 类型擦除(Type erasure)
            • 2.6 变量绑定匹配
            • 3. 模式匹配和Case Class
              • 3.1构造器模式匹配
                • 3.2 Sealed Class的模式匹配
                • 4.模式匹配的其他用法
                  • 4.1 for循环中使用
                    • 4.2 正则表达式中使用
                      • 4.3 异常处理中使用
                        • 4.4 Option类中使用
                        • 总结
                        相关产品与服务
                        容器服务
                        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档