2.8 用一个case语句匹配多个条件
1. 问题描述
当几个匹配条件要求执行相同的业务逻辑,你想要使用业务逻辑的一个副本来匹配条件,而不是为每个case重复业务逻辑
2. 解决方案
将匹配条件放在一行使用|分割
val i = 5 i match { case 1 | 3 | 5 | 7 | 9 => println("odd") case 2 | 4 | 6 | 8 | 10 => println("even") }
同理,该语法也适用于字符串或其他类型
val cmd = "stop" cmd match { case "start" | "go" => println("starting") case "stop" | "quit" | "exit" => println("stopping") case _ => println("doing nothing") }
trait Command case object Start extends Command case object Go extends Command case object Stop extends Command case object Whoa extends Command def executeCommand(cmd: Command) = cmd match { case Start | Go => start() case Stop | Whoa => stop() }
2.9. 将match表达式的值赋值给变量
1. 描述
你想要将匹配表达式返回值其赋值给变量,或使用匹配表达式作为方法的主体
2. 解决方案
在表达式之前插入变量
val evenOrOdd = someNumber match { case 1 | 3 | 5 | 7 | 9 => println("odd") case 2 | 4 | 6 | 8 | 10 => println("even") }
这种方法经常被用作创建短的方法或函数
def isTrue(a: Any) = a match { case 0 | "" => false case _ => true }
2.10 访问匹配表达式默认情况下的值
1. 问题描述
你想要访问默认匹配值,但是使用通配符时无法访问到值
2. 解决方案
在默认情况下指定变量名而非_通配符
i match { case 0 => println("1") case 1 => println("2") case default => println("You gave me: " + default) }
通过给默认匹配变量名称,你可以在语句右侧访问该变量
3. 讨论
在默认匹配时使用变量名而非通配符,若没有给出通配符时,可能抛出异常
2.11 在匹配表达式中使用模式匹配
1. 问题描述
你需要在匹配表达式中匹配一个或多个模式,该模式可以是常量模式、变量模式、构造函数模式、序列模式、元组模式或类型模式等
2. 解决方案
为要匹配的每个模式定义一个case语句
def echoWhatYouGaveMe(x: Any): String = x match { // constant patterns case 0 => "zero" case true => "true" case "hello" => "you said 'hello'" case Nil => "an empty List" // sequence patterns case List(0, _, _) => "a three-element list with 0 as the first element" case List(1, _*) => "a list beginning with 1, having any number of elements" case Vector(1, _*) => "a vector starting with 1, having any number of elements" // tuples case (a, b) => s"got $a and $b" case (a, b, c) => s"got $a, $b, and $c" // constructor patterns case Person(first, "Alexander") => s"found an Alexander, first name = $first" case Dog("Suka") => "found a dog named Suka" // typed patterns case s: String => s"you gave me this string: $s" case i: Int => s"thanks for the int: $i" case f: Float => s"thanks for the float: $f" case a: Array[Int] => s"an array of int: ${a.mkString(",")}" case as: Array[String] => s"an array of strings: ${as.mkString(",")}" case d: Dog => s"dog: ${d.name}" case list: List[_] => s"thanks for the List: $list" case m: Map[_, _] => m.toString // the default wildcard pattern case _ => "Unknown }
上述方法可以匹配不同的类型和模式
3. 讨论
通常使用此技术时,你的方法希望传入的是基类或特性继承的实例,然后case语句引用该基类型的子类型
import java.io.File sealed trait RandomThing case class RandomFile(f: File) extends RandomThing case class RandomString(s: String) extends RandomThing class RandomNoiseMaker { def makeRandomNoise(t: RandomThing) = t match { case RandomFile(f) => playSoundFile(f) case RandomString(s) => speak(s) } }
sealed表示密封的特质,其子类必须全部给出。
有时你可能需要向模式添加变量,你可以用下面的一般语法:
变量名 @ 模式
这表示变量绑定模式,这种模式的含义是按照正常模式进行模式匹配,如果模式匹配成功,就像简单的变量模式一样,将变量设置为匹配对象。
当匹配List类型时,我们可以如下
case list: List[_] => s"thanks for the List: $list"
当我们想要匹配以元素1作为头元素的列表时,我们可能会进行如下匹配
case list: List(1, _*) => s"thanks for the List: $list"
但此时会报错
解决办法是添加变量绑定模式
case list @ List(1, _*) => s"$list"
2.12. 在匹配表达式中使用案例类
1. 问题描述
你希望在匹配表达式中匹配不同的case类(或case对象),如接收actor的消息时
2. 解决方案
根据你的需要可以使用之前的不同模式进行匹配
trait Animal case class Dog(name: String) extends Animal case class Cat(name: String) extends Animal case object Woodpecker extends Animal object CaseClassTest extends App { def determineType(x: Animal): String = x match { case Dog(moniker) => "Got a Dog, name = " + moniker case _:Cat => "Got a Cat (ignoring the name)" case Woodpecker => "That was a Woodpecker" case _ => "That was something else" } println(determineType(new Dog("Rocky"))) println(determineType(new Cat("Rusty the Cat"))) println(determineType(Woodpecker)) }
2.13 将if表达式(守卫)添加到case语句中
1. 问题描述
你希望将匹配逻辑添加到匹配表达式中的case语句中,例如允许一系列数字或匹配模式,但仅当该模式与某些附加条件相匹配时
2. 解决方案
将if表达式添加至case语句中
i match { case a if 0 to 9 contains a => println("0-9 range: " + a) case b if 10 to 19 contains b => println("10-19 range: " + b) case c if 20 to 29 contains c => println("20-29 range: " + c) case _ => println("Hmmm...") }
可以匹配不同的值
num match { case x if x == 1 => println("one, a lonely number") case x if (x == 2 || x == 3) => println(x) case _ => println("some other value") }
也可以在匹配时解析出字段信息
def speak(p: Person) = p match { case Person(name) if name == "Fred" => println("Yubba dubba doo") case Person(name) if name == "Bam Bam" => println("Bam bam!") case _ => println("Watch the Flintstones!") }
3. 讨论
在进行匹配时也可将if放在=>的右侧
case Person(name) => if (name == "Fred") println("Yubba dubba doo") else if (name == "Bam Bam") println("Bam bam!")
2. 14 使用匹配表达式而非isInstanceOf
1. 问题描述
你需要编写匹配一种类型或多个不同类型的代码块
2. 解决方案
你可以使用isInstanceOf来测试对象的类型
if (x.isInstanceOf[Foo]) { do something ...}
然而并不鼓励这样做,更好的办法是使用匹配表达式
def isPerson(x: Any): Boolean = x match { case p: Person => true case _ => false }
或者给出一个继承已知父类的子类,需要根据子类的不同做出不同的操作
trait SentientBeing trait Animal extends SentientBeing case class Dog(name: String) extends Animal case class Person(name: String, age: Int) extends SentientBeing def printInfo(x: SentientBeing) = x match { case Person(name, age) => // handle the Person case Dog(name) => // handle the Dog }
3. 讨论
匹配表达式可以匹配多种类型,而不需要使用isInstanceOf方法
2. 15. 在匹配表达式中使用列表
1. 问题描述
列表数据结构与其他集合数据结构有点不同,它从cons cells开始构建,并以Nil作为结尾,在使用匹配表达式时你想要利用此特性,例如编写递归程序
2. 解决方案
你可以使用如下方法创建List
当编写递归函数时,你可以利用其尾元素是Nil类型对象
3. 讨论
在REPL中构建List时,不要忘记Nil情况
2.16 使用try/catch匹配一个或多个异常
1. 问题描述
在try/catch块中你想要匹配一个或多个异常
2. 解决方案
Scala的try/catch/finally与Java类型,但是在catch中使用的是模式匹配
val s = "Foo" try { val i = s.toInt } catch { case e: Exception => e.printStackTrace }
当你需要匹配多个异常时,在catch添加即可
try { openAndReadAFile(filename) } catch { case e: FileNotFoundException => println("Couldn't find that file.") case e: IOException => println("Had an IOException trying to read that file") }
3. 讨论
Scala中的catch块可以匹配多个异常,如果你不关心发生的异常,可以使用如下方式
try { openAndReadAFile("foo") } catch { case t: Throwable => t.printStackTrace() }
try { val i = s.toInt } catch { case _: Throwable => println("exception ignored") }
2.17 在try/catch/finally块之前声明变量
1. 问题描述
当你需要调用对象的闭包方法,你想要在try块中使用一个对象,并且在finally块中访问它
2. 解决方案
在try之前将字段声明为Option类型,之后在try块中创建一个Some对象
import java.io._ object CopyBytes extends App { var in = None: Option[FileInputStream] var out = None: Option[FileOutputStream] try { in = Some(new FileInputStream("/tmp/Test.class")) out = Some(new FileOutputStream("/tmp/Test.class.copy")) var c = 0 while ({c = in.get.read; c != −1}) { out.get.write(c) } } catch { case e: IOException => e.printStackTrace } finally { println("entered finally ...") if (in.isDefined) in.get.close if (out.isDefined) out.get.close } }
2.18 自定义控制结构
1. 问题描述
你想要自定义控制结构来改善Scala语言
2. 解决方案
假如有一天你不喜欢while结构,而是如下结构
package foo import com.alvinalexander.controls.Whilst._ object WhilstDemo extends App { var i = 0 whilst (i < 5) { println(i) i += 1 } }
此时,whilist就是你自定义的控制结构,你可能采用如下方法定义whilist方法
def whilst(testCondition: => Boolean)(codeBlock: => Unit) { while (testCondition) { codeBlock } }
但是更好的方法是不调用while循环
package com.leesf.controls import scala.annotation.tailrec object Whilst { // 2nd attempt @tailrec def whilst(testCondition: => Boolean)(codeBlock: => Unit) { if (testCondition) { codeBlock whilst(testCondition)(codeBlock) } } }
3. 讨论
在第二版的whilist中,使用了递归调用,假设有一个两个参数的控制结构,当都为真时执行代码块,调用代码如下
doubleif(age > 18)(numAccidents == 0) { println("Discount!") }
doubleif方法则可定义如下
def doubleif(test1: => Boolean)(test2: => Boolean)(codeBlock: => Unit) { if (test1 && test2) { codeBlock } }
其中,codeBlock: => Unit对应的是println("Discount!")
三、总结
本篇博文学习了Scala的控制结构及其细节,同时还学习了自定义控制结构,也谢谢各位园友的观看~