Scala入门系列(五):Scala扩展:模式匹配、偏函数、正则表达式、隐式类、异常处理、高级类型
match表达式
类似Java switch语句
scala中match表达式类似Java中的switch语句,switch的case语句可以处理int,short,byte,char类似的值,但是不能处理long,String等类型。Scala中任何类型都能当作比较用的样本(case),另外每个备选项最后不需要break,因为break是隐含的,防止因为疏忽从一个选择落入另一个中。
不需要break
能够生成值
val firstArg=if(args.length>0) args(0) else ""
firstArg match{
case "salt" => println("pepper")
case "chips" => println("salsa")
case "eggs" => println("bacon")
case _ => println("huh?") //以上未列出的值,使用“_”表示
}
样例类的模式匹配
模式匹配
检查某个值(value)是否匹配某一个模式的机制,一个成功的匹配同时会将匹配值解构为其组成部分
//基本模式匹配
def matchTest(x: Int): String = x match {
case 1 => "one"
case 2 => "two"
case _ => "many"
}
matchTest(3) // many
matchTest(1)
//仅匹配类型
def matchTest3(x: Any): String = x match {
case x:Int => "Int"
case x:String => "String"
case _ => "Any"
}
matchTest3(3.0) // Any
matchTest3(1) // Int
//模式守卫(在模式后面加上if 条件)
def matchTest2(x: Int): String = x match {
case i if i==1 => "one"
case i if i==2 => "two"
case _ => "many"
}
matchTest2(3) // many
matchTest2(1) // one
//样例类的模式匹配
def matchTest4(x: Student)= x match {
case Student(name,19) => println(name)
case Student("Tom",age) => println(age)
case Student(name,age) => println(name,age)
case _ => println("no matches")
}
matchTest4(Student("Jason",19))
matchTest4(Student("Tom",20))
matchTest4(Student("Jimmy",20))
非样例类的模式匹配
单列对象中指定unapply()方法时,称为提取器对象(Extractor Objects)
unapply()方法接收一个实例对象,返回最初创建它所用的参数
class Student(_name:String,_age:Int) {
var name=_name
var age=_age
}
object Student{
def apply(name: String, age: Int): Student = new Student(name, age)
def unapply(arg: Student): Option[(String, Int)] ={
if(arg==null) None else Some(arg.name,arg.age)
}
}
def matchTest(x:Student)=x match {
case Student(name,age) if age<=20 => println("young man")
case Student(name,age) if age>20 => println("old man")
}
matchTest(Student("Jason",19)) //young man
偏函数
偏函数是只对函数定义域的一个子集进行定义的函数
PartialFunction[-A,+B]是一个特质
A为函数定义域,B为偏函数返回值类型
apply()
isDefinedAt()
//偏函数是只对函数定义域的一个子集进行定义的函数。举例来说,List 中可
//能存在不同类型的元素:List(1,2,3,"four"),假设现在需要对数字进行加 1 操作:
//自定义偏函数
val inc = new PartialFunction[Any, Int] {
def apply(any: Any) = any.asInstanceOf[Int]+1
def isDefinedAt(any: Any) = if (any.isInstanceOf[Int]) true else false
}
List(1,2,3,"four").collect(inc)
case语句
//对于普通函数,需要考虑所有输入参数的情况,也就是说,对于函数 A => B,
//它对 A 类型的所有值都有意义;而偏函数 PartialFunction[A, B]仅对 A 类型的部分值有意义
//collect()除了接收一个偏函数,与 map()效果一样。使用偏函数实现上面需求
//的代码如下:
val pf:PartialFunction[Any, Int]={case x:Int=>x+1} //返回一个偏函数
List(1,2,3,"four").collect(pf) //输出List(2,3,4)
//可以发现,对于偏函数 pf 来说,对 Any 类型所有值仅处理了符合要求(x:Int) 的 Int 数据,返回 Int 类型。
//其中,“{ case pattern=> ??? }”表达式是一个整体,包括外面的大括号。这
//里 case 后面的写法与模式匹配完全相同,可以为不同的匹配模式。为了方便描
//述,统称为 case 语句、或者函数字面量。
//可以认为,一个 case 语句就是一个独立的匿名函数,如果有一组 case 语句
//的话,从效果上看,构建出的这个匿名函数会有多种不同的参数列表,每一个
//case 对应一种参数列表,参数是 case 后面的变量声明,其值是通过模式匹配赋
//予的。
//实际上,case 语句返回的是一个特质 PartialFunction(继承自(A=>B)),其本
//质还是一个函数(scala.Function1),所以当成函数用没有一点问题。
//case 语句是创建 PartialFunction 的便捷方式,原始自定义创建 PartialFunction
方式如下。
val inc = new PartialFunction[Any, Int] {
def apply(any: Any) = any.asInstanceOf[Int]+1
def isDefinedAt(any: Any) = if (any.isInstanceOf[Int]) true else false
}
List(1,2,3,"four").collect(inc)
PartialFunction 特质规定了两个要实现的方法:apply 和 isDefinedAt。
//1)isDefinedAt 用来告知调用方这个偏函数接受参数的范围,可以是类型也
//可以是值,在我们这个例子中我们要求这个 inc 函数只处理 Int 型的数据。
//2)apply 方法用来描述对已接受的值如何处理,在我们这个例子中,我们只
//是简单的把值+1,注意,非 Int 型的值已被 isDefinedAt 方法过滤掉了,所以不用
//担心类型转换的问题。
//case 语句是如何转成偏函数的呢?如下代码所示。
val pf:PartialFunction[Any, Int]={case x:Int=>x+1}
//对于上面的 pf,事实上 Scala 编译器通过两次模式匹配,将表达式转换为偏
//函数。一次是实函数的实现(apply),另一次测试函数定义域是否被定义(isDefineAt)。
new PartialFunction[Any, Int] {
def apply(xs: Any) = xs match {
case x:Int => x+1
}
def isDefinedAt(xs: Any) = xs match {
case x:Int=> true
case _ => false
} }
注解(Annotation)
Scala标准库注解包——scala.annotation
注解语法:@注解名称(注解参数…)
常用注解
@throws、@deprecated、@unchecked、@SerialVersionUID……
可使用注解的地方:类、方法、方法参数、字段、局部变量
object DeprecationDemo extends App{
@deprecated("deprecation message", "release # which deprecates method")
def hello = "hola"
@throws(classOf[Exception])
def test(){}
}
运算符
在Scala中,运算符即是方法。任何具有单个参数的方法都可以用作中缀运算符
10.+(1)=>10+1
定义和使用运算符
case class Vec(val x: Double, val y: Double) {
def +(that: Vec) = new Vec(this.x + that.x, this.y + that.y)
def add(that: Vec) = new Vec(this.x + that.x, this.y + that.y)
}
val vector1 = Vec(1.0, 1.0)
val vector2 = Vec(2.0, 2.0)
val vector3 = vector1 + vector2 // 或者 vector1 add vector2
vector3.x // 3.0
vector3.y // 3.0
Scala正则表达式
正则表达式的语法在大部分编程语言中都是相同的,所以这里不介绍正则表
达式的写法,而是介绍正则表达式在 Scala 中的几种用法。在 Scala 中常见的用
法包括三种:
使用 String 类的 matches()方法
使用 Regex(scala.util.matching.Regex)的提取器进行模式匹配
使用 Regex API
1.使用 String.matches()
"!123".matches("[a-zA-Z0-9]{4}") //false
"34Az".matches("[a-zA-Z0-9]{4}") //true
这种方式常用于字符串格式验证,返回结果为此字符串是否匹配给定的正则
表达式。matches()的参数为正则表达式。
2.使用 Regex 的提取器
Scala 的正则表达式类为 scala.util.matching.Regex,可以通过向 Regex 的构造器传递正则表达式字符串构造 Regex 对象。
import scala.util.matching.Regex
val decimal=new Regex("(-)?(\d+)(.\d*)?")
如果正则表达式包含了很多转义符可以使用三引号。
val decimal=new Regex("""(-)?(\d+)(\.\d*)?""")
最为简单的方式是直接使用 StringLike 的 r 方法,可以将字符串转为 Regex
对象。
val decimal="""(-)?(\d+)(\.\d*)?""".r
如下图所示,在 scala.Predef 中定义了隐式转换函数,可将 String 转换为
StringOps,而 StringOps 继承了 StringLike。StringLike 封装了一系列对字符串的操作,其中之一便是 r 方法,可将字符串转为 Regex 对象,如果 r()方法指定多个字符串时,表示对正则表达式的匹配分组进行命名
Regex共有四个提取器,如图所示
unapplySeq(CharSequence):Option[List[String]]
unapplySeq(Char):Option[List[Char]]
unapplySeq(Match):Option[List[String]]
unapplySeq(Any):Option[List[String]]
这四个提取器分别作用于字符串,字符,Match 及 Any 类型的模式匹配。以
unapplySeq(CharSequence)为例,表示待匹配数据类型为字符串。
val p1 = "ab*c".r
val p1Matches = "abbbc" match {
case p1() => true // no groups
case _ => false
}//输出 :true
当匹配目录为字符串“abbbc”时,“case p1()”表示正则表达式正确匹配,但没有更多分组信息,注意从分组编号为 1 开始提取,而分组 0 为完整匹配结果“abbbc”。
unapplySeq(CharSequence)源码如下图所示:
def unapplySeq(s: CharSequence): Option[List[String]] = s match {
case null =>None
case_=>
val m = pattern matcher s
if (runMatcher(m)) Some((1 to m.groupCount).toList map m.group)
else None
}
注意源码中:
“Some((1 to m.groupCount).toList map m.group)” 返回“List()”即空的 List。
所以“case p1() => true”被执行。
当我们对 Regex 进行分组定义如下:
val p2 = "a(b*)c".r
val p2Matches = "abbbc" match {
case p2(b) => Some(b) // one group
case _=> None
}//输出 :Some(bbb)
注意 p2 中带有一个分组“(b*)”,所以“case p2(b)”表示正则表达式正确匹配,并捕获一个分组内容同时赋给变量“b”,如果 p2 中有多个分组,则依次往后给出对应变量即可,如“case p2(b,c,…)”。例如:
val p3 = "(a(b*))c".r
val p3Matches = "abbbc" match {
case p3(a,b)=>Some((a,b))
case _=>None
}//输出 :Some((abbb,bbb))
3.使用 Regex API
1)Regex 查找
findFirstMatchIn() 返回第一个匹配(Option[Match])
findAllMatchIn() 返回所有匹配结果(Regex.Match)
findAllIn() 返回所有匹配结果(String)
Match 是对匹配结果的描述,主要方法有:
groupCount:获取所有捕获的分组总数
group():获取指定的分组,分组由 0 开始编号
subgroups:所有捕获的分组,不包括分组 0
val p3 = "a((b*))c".r
val m=p3.findFirstMatchIn("abbc").get
(0 to m.groupCount).map(m.group).foreach(println) //输出:abbc bb bb
m.subgroups.foreach(println) //输出:bb bb
2)多 Match 分组处理
下面识别“name:Jason,age:19,……”中的键值对。很容易想到匹配一个键值对使用“xxx:xxx”这种模式,应该注意对于 JSON 中键值对总是重复出现的,所以单纯使用findFirstMatchIn 无法满足要求,换 findAllMatchIn 实现。如下所示。
import scala.util.matching.Regex
val studentPattern:Regex="([0-9a-zA-Z-#() ]+):([0-9a-zA-Z-#()]+)".r
val input="name:Jason,age:19,weight:100"
for(patternMatch<studentPattern.findAllMatchIn(input)){
println(s"key: ${patternMatch.group(1)} value:${patternMatch.group(2)}"
)}
3)Regex 字符串替换
使用正则表达式进行字符串替换算是家常便饭了,常见的需求包括,批量替
换电话,住址信息等等。Regex 提供的字符串替换 API 如下图所示。
replacelln(CharSequence, String): String
replaceAllIn(CharSequence, Match => String): String
replaceSomeln(CharSequence, Match => Option[String]): String
replaceFirstIn(CharSequence, String): String
以 replaceAllIn 为例,替换用户的住址信息:
"[0-9]+".r.replaceFirstIn("234 Main Street Suite 2034", "567")//234->567
"[0-9]+".r.replaceAllIn("234 Main Street Suite 2034", "567") //234、2034->567
当使用 replaceAllIn 时要小心,因为上面的“2034”也被替换了,这与我们的要求可能有些出入,所以有时需要一些额外的处理,比如:
"[0-9]+".r.replaceAllIn("234 Main Street Suite 2034", m=>{
if(m.group(0).length==3) "567" else m.group(0)
})
隐式类
此处适当回顾前面隐式参数与隐式函数。
隐式类指的是用 implicit 关键字修饰的类。在对应的作用域内,带有这个关
键字的类的主构造器可用于隐式转换。隐式类有着多种限制条件:
只能在类、Trait、对象(单例对象、包对象)内部定义
构造器只能携带一个非隐式参数
隐式类不能是 case class
在同一作用域内,不能有任何方法、成员或对象与隐式类同名隐式类可以为目标对象扩展新的方法。如下所示,我们为 String 类增加了increment 方法。
object Stringutils {
implicit class StringImprovement(val s:String){//隐式类
def increment=s.map(x=>(x +1).toChar)
} }
object Main extends App{
import Stringutils._
println("mobin".increment) //输出:npcjo
}
编译器在 mobin 对象调用 increment 时发现对象上并没有 increment 方法,
此时编译器就会在作用域范围内搜索隐式实体,发现有符合的隐式类可以用来转
换成带有 increment 方法的 StringImprovement 类,最终调用 increment 方法。
异常处理
Scala 有多种异常处理方式,包括 try…catch…、Either、allCatch。
1.try…catch…与 Java 类似,但其中 catch 子句后需接一个偏函数,格式如下:
try{
//todo
}catch{
case ex:异常类型=>{ //todo }
……
}finally{
//todo
}
2.Either
Either[A, B] 表示要么包含一个类型为 A 的实例,要么包括一个类型为 B 的实例。Either 有两个子类型,Left、Right,如果 Either[A, B]对象包含的是 A 的实例,则它是 Left 实例,否则是 Right 实例
Either 用于异常处理时,一般约定:Left 代表出错的情况,Right 代表成功的情况。
def divide(x:Int): Either[String,Int] ={
if(x==0)
Left("除数不能为 0")
else
Right(100/x)
}
def test(x:Int)=divide(x) match {
case Left(errMsg)=>println(errMsg)
case Right(result)=>println(result)
}
test(0)
test(1)
3.allCatch
allCatch 是 scala.util.control.Exception 单例对象的方法。返回 Catch 类,主要
操作如下图所示。
Catch[T](Catcher[T], Option[Finally], Throwable => Boolean)
pf: Catcher[T]
fin: Option[Finally]
rethrow: Throwable => Boolean
name
or[U >: T](Catcher[U]): Catch[U]
or[U >: T](Catch[U]): Catch[U]
apply[U >: T](=> U):∪
andFinally(=> Unit): Catch[T]
opt[∪ >: T](=> U): Option[U]
either[∪>: T](=> U): Either[Throwable, U]
withTry[U >: T](=> U): Try[U]
withApply[U](Throwable => U): Catch[U]
toOption: Catch[Option[T]]
toEither: Catch[Either[Throwable, T]]
toTry: Catch[Try[T]]
1)opt()方法
将此 Catch 捕获逻辑应用于所提供的主体,将结果映射到“Option[T]”:如果捕获了任何异常,则映射到“None”,否则映射到“Some(T)”。
scala.util.control.Exception.allCatch.opt("42".toInt) // Some(42)
scala.util.control.Exception.allCatch.opt("42a".toInt) // None
2)toTry()方法
如果发生异常,封装为 Failure 对象,否则执行表达式。
scala.util.control.Exception.allCatch.toTry(“42”.toInt) // 42
scala.util.control.Exception.allCatch.toTry(“42a”.toInt) // Failure (e)
3)withTry()方法
返回 scala.util.Try 对象,其有两个子类型,Success 和 Failure,分别表示运行
正常与异常,本质是调用 toTry(Success(body))。
scala.util.control.Exception.allCatch.withTry("42".toInt) // Success(42)
scala.util.control.Exception.allCatch.withTry("42a".toInt) // Failure (e)
4)either()方法
返回两种类型,正确为 Right,有错误为 Left。
scala.util.control.Exception.allCatch.either("42".toInt) // Right(42)
scala.util.control.Exception.allCatch.either("42a".toInt) // Left(e)
高级类别
1.结构类型
在 Scala 中可以像如下方式去定义一个函数:def 函数名(变量名:{抽象函数},其他参数列表)其中,{抽象函数}称之为结构类型,是指一组关于抽象方法、字段和类型的
规格说明。通常用在定义方法时,要求传参具有某种行为,但又不想使用类或者
接口去限制,可以使用结构类型,表示任意具有该结构的类型都可以满足要求。
例如下而代码创建了一个结构类型:
{
def sayHello(name:String):Unit
}
可以将上面结构类型做为函数的参数类型。
def f(a:{def sayHello():Unit}){a.sayHello}
调用时需给出与任意与 a 相同结构的类型(包含 sayHello()即可)。
f(new {def sayHello():Unit={println("hello")}}) //hello
其中,“new {…}”实例化了一个匿名对象,并具有“sayHello()”方法。又如:
object obj1{
def sayHello():Unit={println("hello,i’m obj1")} }
f(obj1) //hello,i’m obj1
通常使用 type 关键字为结构类型定义别名,如下所示。
type X={def sayHello():Unit}
def f(a:X){a.sayHello}
复合类型
如果一个类如下定义:
class A extends B with C with D with E
其中“B with C with D with E” 这种形式的类型便称为复合类型(compound type)或者也叫交集类型(intersection type)。复合类型可以由多个对象类型构成,这些对象类型可以有单个细化,用于缩短已有对象成员的签名。格式为:A with B with C … {refinement }其中“{refinement}”可以为结构类型。复合类型也可作为函数参数类型。
trait X1
trait X2
def test(x: X1 with X2) = {println("ok")}
test(new X1 with X2)
object A extends X1 with X2
test(A)
//使用 type 定义复合类型:
trait X1
trait X2
type X=X1 with X2
def test(x: X) = {println("ok")}
test(new X1 with X2)
class A extends X1 with X2
val a = new A
test(a) //ok
type XX=X1 with X2 { def close():Unit } //复合类型中也可包含结构类型
def test(x: XX) = {println("ok");x.close}
test(new X1 with X2 {def close()={println("closed")}}) //ok closed