9.7 使用case
类进行模式匹配
case
类是一种特殊的类,用于case
表达式的模式匹配。假定我们要接收和处理股票交易信息。买卖消息通常会带有一些信息,诸如股票名称、数量。把这些信息存到对象里会很方便,但是如何对他们进行模式匹配呢?这就是case
类的目的所在了。它们是模式匹配器(pattern matcher)可以识别和匹配的类。下面有几个case
类的例子:
PatternMatching/TradeProcessor.scala
abstract case class Trade()
case class Sell(stockSymbol: String, quantity: Int) extends Trade
case class Buy(stockSymbol: String, quantity: Int) extends Trade
case class Hedge(stockSymbol: String, quantity: Int) extends Trade
这里将Trade
定义为abstract
,因为不需要它的实例。从它继承出Sell
、Buy
和Hedge
。这三个都以股票代码和数量作为参数。
现在,在case
语句里使用这些类,如下:
PatternMatching/TradeProcessor.scala
class TradeProcessor {
def processTransaction(request : Trade) {
request match {
case Sell(stock, 1000) => println("Selling 1000-units of " + stock)
case Sell(stock, quantity) =>
printf("Selling %d units of %s\n", quantity, stock)
case Buy(stock, quantity) if (quantity > 2000) =>
printf("Buying %d (large) units of %s\n", quantity, stock)
case Buy(stock, quantity) =>
printf("Buying %d units of %s\n", quantity, stock)
}
}
}
这里,用request
匹配Sell
和Buy
。如果接收到的股票代码和数量得到匹配,就会分别存储到模式变量stock
和quantity
里。这里还用到特定常量值(比如quantity
为1000
)或是卫述句(比如检查if quantity > 2000
)进行匹配。下面是使用TradeProcessor
类的例子:
PatternMatching/TradeStock.scala
val tradeProcessor = new TradeProcessor
tradeProcessor.processTransaction(Sell("GOOG", 500))
tradeProcessor.processTransaction(Buy("GOOG", 700))
tradeProcessor.processTransaction(Sell("GOOG", 1000))
tradeProcessor.processTransaction(Buy("GOOG", 3000))
上面代码的输出如下:
Selling 500 units of GOOG
Buying 700 units of GOOG
Selling 1000-units of GOOG
Buying 3000 (large) units of GOOG
processTransaction()
并没有匹配Trades
所有可能的类型,这里跳过了Hedge
。在运行时,如果接收到Hedge
,就会带来问题。不过,Scala并不知道有多少case
类继承Trade
。毕竟,在其他文件里也是可以继承case
类的。然而,如果我们告诉了Scala:“除了这个文件中已有的类之外,不会再有更多的类出现”,Scala就可以帮助我们解决这个问题。要做到这一点,可以使用一个不常见的组合sealed abstract
,如下所示:
sealed abstract case class Trade()
case class Sell(stockSymbol: String, quantity: Int) extends Trade
case class Buy(stockSymbol: String, quantity: Int) extends Trade
case class Hedge(stockSymbol: String, quantity: Int) extends Trade
现在,如果编译TradeProcessor
类,Scala编译器会发出严重警告“warning: match is not exhaustive!”添加一个Hedge
的case
就可以修复这个警告。在上面的代码里,所有具体的case
类都接收了参数。如果有个没有参数的case
类,记得用的时候要放个括号(参见附录A)。下面的例子就有个没有任何参数的case
类:
PatternMatching/ThingsAcceptor.scala
import scala.actors._
import Actor._
case class Apple()
case class Orange()
case class Book ()
class ThingsAcceptor {
def acceptStuff(thing: Any) {
thing match {
case Apple() => println("Thanks for the Apple")
case Orange() => println("Thanks for the Orange")
case Book() => println("Thanks for the Book")
case _ => println("Excuse me, why did you send me a " + thing)
}
}
}
下面的代码里,有一个调用,忘了在Apple
后面加括号:
PatternMatching/UseThingsAcceptor.scala
val acceptor = new ThingsAcceptor
acceptor.acceptStuff(Apple())
acceptor.acceptStuff(Book())
acceptor.acceptStuff(Apple)
上面调用的结果如下:
Thanks for the Apple
Thanks for the Book
Excuse me, why did you send me a <function>
一旦忘记了括号,传的就不是case
类的实例,而是它的伴生对象。这个伴生对象混入了scala.Function0 trait
,这意味着它可以当作函数用。所以,最终传入的是一个函数,而不是case
类的实例。如果acceptStuff()
方法接收的是名为Thing
的case
类的实例,这不会有问题。不过,在actor间传递消息时,我们无法以类型安全的方式在编译时控制发送给actor的东西。因此,传递case
类时,请小心一点。
虽然随着Scala编译器的逐步更新,可能会修复上面的问题,但这种边缘情况依然会出现。这也充分说明对代码进行测试的重要性,即便是对静态类型语言也是一样。(参见第12章“Scala单元测试”。)