Scala模式匹配泛型类型擦除问题

Posted 过往记忆大数据

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Scala模式匹配泛型类型擦除问题相关的知识,希望对你有一定的参考价值。

  在中一个很强大的功能就是模式匹配,本文并不打算介绍模式匹配的概念以及如何使用。本文的主要内容是讨论模式匹配泛型类型擦除问题。先来看看泛型类型擦除是什么情况:

 
   
   
 
  1. scala> def test(a:Any) = a match {

  2. | case a :List[String] => println("iteblog is ok");

  3. | case _ =>

  4. |}

  按照代码的意思应该是匹配List[String]类型的数据,但是这个test(List("Iteblog")) 和 test(List(2015))都是可以通过的。

1 scala> test(List("Iteblog"))
2 iteblog is ok
3
4 scala> test(List(2015))
5 iteblog is ok

  这是因为类型被擦除了,你在编译这个代码的时候也会看到编译器给的提示:

1 <console>:14: warning: non-variable type argument String in typepattern List[String] is unchecked since it is eliminated by erasure
2 casea :List[String] => println("iteblog is ok");

  上面测试结果都得到了结果,虽然不是我们要的,但是并没有出现什么异常。下面的代码却会出现异常:

 
   
   
 
  1. scala> case class Container[+A](value: A)

  2. defined class Container

  3. scala> val double = Container(3.3)

  4. double: Container[Double] = Container(3.3)

  5. scala> double match {

  6. | case c: Container[String] => println(c.value.toUpperCase)

  7. | case c: Container[Double] => println(math.sqrt(c.value))

  8. | case _ => println("_")

  9. | }

  因为类型信息被编译器擦除了,所以你的double虽然是Container[Double]类型的,但是只能匹配到第一个case,而你想把一个double转换成String,所以会出现以下的异常:

01 java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.String
02 at $iwC$$iwC$$iwC$$iwC$$iwC$$iwC.<init>(<console>:19)
03 at $iwC$$iwC$$iwC$$iwC$$iwC.<init>(<console>:27)
04 at $iwC$$iwC$$iwC$$iwC.<init>(<console>:29)
05 at $iwC$$iwC$$iwC.<init>(<console>:31)
06 at $iwC$$iwC.<init>(<console>:33)
07 at $iwC.<init>(<console>:35)
08 at <init>(<console>:37)
09 at .<init>(<console>:41)
10 at .<clinit>(<console>)
11 at .<init>(<console>:7)
12 at .<clinit>(<console>)
13 at $print(<console>)
14 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
15 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
16 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
17 at java.lang.reflect.Method.invoke(Method.java:606)
18 at org.apache.spark.repl.SparkIMain$ReadEvalPrint.call(SparkIMain.scala:1065)
19 at org.apache.spark.repl.SparkIMain$Request.loadAndRun(SparkIMain.scala:1340)
20 at org.apache.spark.repl.SparkIMain.loadAndRunReq$1(SparkIMain.scala:840)
21 at org.apache.spark.repl.SparkIMain.interpret(SparkIMain.scala:871)
22 at org.apache.spark.repl.SparkIMain.interpret(SparkIMain.scala:819)
23 at org.apache.spark.repl.SparkILoop.reallyInterpret$1(SparkILoop.scala:857)
24 at org.apache.spark.repl.SparkILoop.interpretStartingWith(SparkILoop.scala:902)
25 at org.apache.spark.repl.SparkILoop.reallyInterpret$1(SparkILoop.scala:875)
26 at org.apache.spark.repl.SparkILoop.interpretStartingWith(SparkILoop.scala:902)
27 at org.apache.spark.repl.SparkILoop.reallyInterpret$1(SparkILoop.scala:875)
28 at org.apache.spark.repl.SparkILoop.interpretStartingWith(SparkILoop.scala:902)
29 at org.apache.spark.repl.SparkILoop.reallyInterpret$1(SparkILoop.scala:875)
30 at org.apache.spark.repl.SparkILoop.interpretStartingWith(SparkILoop.scala:902)
31 at org.apache.spark.repl.SparkILoop.reallyInterpret$1(SparkILoop.scala:875)
32 at org.apache.spark.repl.SparkILoop.interpretStartingWith(SparkILoop.scala:902)
33 at org.apache.spark.repl.SparkILoop.command(SparkILoop.scala:814)
34 at org.apache.spark.repl.SparkILoop.processLine$1(SparkILoop.scala:657)
35 at org.apache.spark.repl.SparkILoop.innerLoop$1(SparkILoop.scala:665)
36 at org.apache.spark.repl.SparkILoop.org$apache$spark$repl$SparkILoop$$loop(SparkILoop.scala:670)
37 at org.apache.spark.repl.SparkILoop$$anonfun$org$apache$spark$repl$SparkILoop$$process$1.apply$mcZ$sp(SparkILoop.scala:997)
38 at org.apache.spark.repl.SparkILoop$$anonfun$org$apache$spark$repl$SparkILoop$$process$1.apply(SparkILoop.scala:945)
39 at org.apache.spark.repl.SparkILoop$$anonfun$org$apache$spark$repl$SparkILoop$$process$1.apply(SparkILoop.scala:945)
40 at scala.tools.nsc.util.ScalaClassLoader$.savingContextLoader(ScalaClassLoader.scala:135)
41 at org.apache.spark.repl.SparkILoop.org$apache$spark$repl$SparkILoop$$process(SparkILoop.scala:945)
42 at org.apache.spark.repl.SparkILoop.process(SparkILoop.scala:1059)
43 at org.apache.spark.repl.Main$.main(Main.scala:31)
44 at org.apache.spark.repl.Main.main(Main.scala)
45 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
46 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
47 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
48 at java.lang.reflect.Method.invoke(Method.java:606)
49 at org.apache.spark.deploy.SparkSubmit$.org$apache$spark$deploy$SparkSubmit$$runMain(SparkSubmit.scala:673)
50 at org.apache.spark.deploy.SparkSubmit$.doRunMain$1(SparkSubmit.scala:180)
51 at org.apache.spark.deploy.SparkSubmit$.submit(SparkSubmit.scala:205)
52 at org.apache.spark.deploy.SparkSubmit$.main(SparkSubmit.scala:120)
53 at org.apache.spark.deploy.SparkSubmit.main(SparkSubmit.scala)

那如何来处理这种问题呢?其实你可以用下面的代码解决:

1 def matchContainer[A: Manifest](c: Container[A]) = c match {
2 case c: Container[String] ifmanifest <:< manifest[String] => println(c.value.toUpperCase)
3 case c: Container[Double] ifmanifest <:< manifest[Double] => println(math.sqrt(c.value))
4 case c: Container[_] => println("other")
5 }

它可以判断出c的类型,但是Manifest已经被标记为deprecated,我们可以使用TypeTag替代Manifest:

1 import reflect.runtime.universe._
2 def matchContainer[A: TypeTag](c: Container[A]) = c match {
3 case c: Container[String] if typeOf[A] <:< typeOf[String] => println(c.value.toUpperCase)
4 case c: Container[Double] if typeOf[A] <:< typeOf[Double] => println(math.sqrt(c.value))
5 case c: Container[_] => println("other")
6 }

不过TypeTags 是线程不安全的,可以参见:http://docs.scala-lang.org/overviews/reflection/thread-safety.html

如果在多线程环境下,我们不能使用上面的代码。我们可以用下面代码解决:

1 var container: Container[Any] = double
2 container match {
3 case Container(x: String) => println("string")
4 case Container(x: Double) => println("double")
5 case _ => println("iteblog")
6 }


以上是关于Scala模式匹配泛型类型擦除问题的主要内容,如果未能解决你的问题,请参考以下文章

在Scala中对列表/序列进行模式匹配时解决类型擦除问题

Java泛型:类型擦除

Scala的类与类型

scala中的@specialized

Scala:抽象类型模式 A 未选中,因为它已被擦除消除

Scala:基础知识03