假设我们有一个通用类Container
:
case class Container[+A](value: A)
然后,我们希望将Container
与Double
和Container
Any
进行模式匹配:
val double = Container(3.3)
var container: Container[Any] = double
为此,我们通常会写:
container match {
case c: Container[String] => println(c.value.toUpperCase)
case c: Container[Double] => println(math.sqrt(c.value))
case _ => println("_")
}
但是,编译器会给出两个警告,前两个案例各一个。例如,第一个警告说:“类型模式容器[String]中的非变量类型参数字符串未被选中,因为它被擦除”。由于擦除,在运行期间不可能区分不同类型的容器并且第一个捕获物将匹配。因此,Container[Double]
类型的容器将匹配第一个案例,该案例会捕获Container[String]
个对象,因此将toUpperCase
和{{Double
上调用java.lang.ClassCastException
方法1}}将被抛出。
如何匹配特定类型参数化的Container
?
答案 0 :(得分:30)
一般来说rarry的答案是正确的,但是对于你的情况它可以简化,因为你的容器只包含一个泛型类型的值,所以你可以直接匹配该值的类型:
container match {
case Container(x: String) => println("string")
case Container(x: Double) => println("double")
case _ => println("w00t")
}
答案 1 :(得分:26)
也许这会有所帮助
def matchContainer[A: Manifest](c: Container[A]) = c match {
case c: Container[String] if manifest <:< manifest[String] => println(c.value.toUpperCase)
case c: Container[Double] if manifest <:< manifest[Double] => println(math.sqrt(c.value))
case c: Container[_] => println("other")
}
修改强>
As Impredicative指出,Manifest已被弃用。相反,您可以执行以下操作:
import reflect.runtime.universe._
def matchContainer[A: TypeTag](c: Container[A]) = c match {
case c: Container[String] if typeOf[A] <:< typeOf[String] => println("string: " + c.value.toUpperCase)
case c: Container[Double] if typeOf[A] <:< typeOf[Double] => println("double" + math.sqrt(c.value))
case c: Container[_] => println("other")
}
答案 2 :(得分:12)
可能的解决方法是使用isInstanceOf
和asInstanceOf
。
container match {
case Container(x) if x.isInstanceOf[String] =>
println(x.asInstanceOf[String].toUpperCase)
case Container(x) if x.isInstanceOf[Double] =>
println(math.sqrt(x.asInstanceOf[Double]))
case _ => println("_")
}
这样可行,但它看起来并不优雅。 Scala的创建者Martin Odersky教授说应该避免使用isInstanceOf
和asInstanceOf
。
正如Rob Norris所指出的那样,在Coursera的“ Scala中的函数编程”课程的论坛上,按类型匹配是一种不好的做法:case foo: Bar => ...
。 Scala鼓励利用静态类型并避免在运行时检查类型。这符合Haskell / ML世界的哲学。而不是匹配类型,case
子句应匹配构造函数。
要解决Container
匹配问题,可以定义每种类型的特殊容器:
class Container[+A](val value: A)
case class StringContainer(override val value: String)
extends Container(value)
case class DoubleContainer(override val value: Double)
extends Container(value)
现在构造函数将匹配,而不是类型:
container match {
case StringContainer(x) => println(x.toUpperCase)
case DoubleContainer(x) => println(math.sqrt(x))
case _ => println("_")
}
显然,我们可以在两个对象unapply
和StringContainer
中定义DoubleContainer
个方法,并使用与上面相同的匹配,而不是扩展Container
类:< / p>
case class Container[+A](val value: A)
object StringContainer {
def unapply(c: Container[String]): Option[String] = Some(c.value)
}
object DoubleContainer {
def unapply(c: Container[Double]): Option[Double] = Some(c.value)
}
但是,由于JVM类型擦除,这不起作用。
引用我这个答案的Rob Norris帖子的引用可以在这里找到:https://class.coursera.org/progfun-002/forum/thread?thread_id=842#post-3567。不幸的是,除非您参加Coursera课程,否则无法访问它。
答案 3 :(得分:4)
注意:您还可以选择使用Miles Sabin的Shapeless library already mentioned by Miles in 2012 here}。
您可以在Ways to pattern match generic types in Scala
的“Jaakko Pallari”中看到一个示例
Typeable
是一个类型类,可以将值从Any
类型转换为特定类型。
转换操作的结果是Option
,其中Some
值将包含成功转换的值,None
值表示转换失败。的提取器
TypeCase
网桥Typeable
和模式匹配。它本质上是Typeable
实例
import shapeless._
def extractCollection[T: Typeable](a: Any): Option[Iterable[T]] = {
val list = TypeCase[List[T]]
val set = TypeCase[Set[T]]
a match {
case list(l) => Some(l)
case set(s) => Some(s)
case _ => None
}
}
val l1: Any = List(1, 2, 3)
val l2: Any = List[Int]()
val s: Any = Set(1, 2, 3)
extractCollection[Int](l1) // Some(List(1, 2, 3))
extractCollection[Int](s) // Some(Set(1, 2, 3))
extractCollection[String](l1) // None
extractCollection[String](s) // None
extractCollection[String](l2) // Some(List()) // Shouldn't this be None? We'll get back to this.
虽然
Typeable
可能看起来具有解决类型擦除所需的功能,但它仍然与任何其他运行时代码具有相同的行为。
这可以在前面的代码示例的最后几行中看到,其中空列表被识别为字符串列表,即使它们被指定为整数列表。这是因为Typeable
强制转型基于列表的值。如果列表为空,那么自然就是一个有效的字符串列表和一个有效的整数列表(或任何其他列表)