为什么要打印wtf?模式匹配不适用于结构类型吗?
"hello" match {
case s: { def doesNotExist(i: Int, x: List[_]): Double } => println("wtf?")
case _ => println("okie dokie")
}
答案 0 :(得分:19)
在Scala解释器中使用未经检查的警告(scala -unchecked
)运行此示例会产生以下警告:warning: refinement AnyRef{def doesNotExist(Int,List[_]): Double} in type pattern is unchecked since it is eliminated by erasure
。不幸的是,这样的泛型类型在运行时无法检查,因为JVM没有具体的泛型。
JVM在此模式匹配中看到的所有内容是:
"hello" match {
case s: Object => ...
case annon: Object => ...
}
编辑:在回复您的评论时,我一直在考虑解决方案但是没有时间在昨天发布。不幸的是,即使应该工作,编译器也无法注入正确的Manifest
。
您要解决的问题是比较某个对象是否属于给定的结构类型。这是我一直在考虑的一些代码(Scala 2.8-r20019,因为Scala 2.7.6.final在玩了类似的想法时几次撞到我身上)
type Foo = AnyRef { def doesNotExist(i: Int, x: List[_]): Double }
def getManifest[T](implicit m: Manifest[T]) = m
def isFoo[T](x: T)(implicit mt: Manifest[T]) =
mt == getManifest[Foo]
方法isFoo
基本上比较了x
的班级Foo
的清单。在理想世界中,结构类型的清单应该等于包含所需方法的任何类型的清单。至少那是我的思路。不幸的是,这无法编译,因为编译器在调用Manifest[AnyRef]
时会注入Manifest[Foo]
而不是getManifest[Foo]
。有趣的是,如果您不使用结构类型(例如,type Foo = String
),则此代码将按预期编译并运行。我将在某个时候发布一个问题,看看为什么这会因结构类型而失败 - 这是一个设计决策,还是只是实验反射API的一个问题。
如果不这样做,你总是可以使用Java反射来查看对象是否包含方法。
def containsMethod(x: AnyRef, name: String, params: java.lang.Class[_]*) = {
try {
x.getClass.getMethod(name, params: _*)
true
}
catch {
case _ => false
}
}
按预期工作:
containsMethod("foo", "concat", classOf[String]) // true
containsMethod("foo", "bar", classOf[List[Int]]) // false
......但这不是很好。
另请注意,结构类型的结构在运行时不可用。如果您有方法def foo(x: {def foo: Int}) = x.foo
,则在删除后会得到def foo(x: Object) = [some reflection invoking foo on x]
,类型信息会丢失。这就是为什么首先使用反射的原因,因为您必须在Object
上调用方法,而JVM不知道Object
是否具有该方法。
答案 1 :(得分:9)
如果您将不得不使用反射,您至少可以使用提取器使其看起来更漂亮:
object WithFoo {
def foo(){
println("foo was called")
}
}
object HasFoo {
def containsMethod(x: AnyRef, name: String, params: Array[java.lang.Class[_]]) : Boolean = {
try {
x.getClass.getMethod(name, params: _*)
true
} catch {
case _ => false
}
}
def unapply(foo:AnyRef):Option[{def foo():Unit}] = {
if (containsMethod(foo, "foo", new Array[Class[_]](0))) {
Some(foo.asInstanceOf[{def foo():Unit}])
} else None
}
}
WithFoo.asInstanceOf[AnyRef] match {
case HasFoo(foo) => foo.foo()
case _ => println("no foo")
}