Scala入门系列(五):Scala扩展:模式匹配、偏函数、正则表达式、隐式类、异常处理、高级类型

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值