ClassCastException在更高阶的泛型函数中

时间:2012-03-10 09:45:58

标签: generics scala

我有一个代码试图将一个函数包装在另一个执行动态类型检查的函数中:

class Base

class Foo extends Base

class Bar extends Base

object Main{
  def checker[A <: Base]( func : A => String) : Base => String =
    (b : Base) => b match {
      case a : A => func(a)
      case _ => "error"
    }

  def fooFunc(f : Foo) = "It's a foo"

  def main(arg : Array[String]) {
    val check = checker(fooFunc)

    println(check(new Foo) + ", " + check(new Bar))
  }
}

这会产生以下错误:

Exception in thread "main" java.lang.ClassCastException: Bar cannot be cast to Foo
    at Main$$anonfun$1.apply(Main.scala:17)
    at Main$.main(Main.scala:19)
    at Main.main(Main.scala)

如果我删除了type参数并在checker的定义中将A替换为Foo,则效果很好。但是,如果我保留了类型参数但省略了函数参数并将func(a)替换为“good”,那么我对Foo和Bar都“好”。

这是什么叫做类型擦除?我对这个概念并不熟悉。
此外,我很乐意听到解决方案。

3 个答案:

答案 0 :(得分:3)

我找到了使用清单的方法。

class Base

class Foo extends Base

class Bar extends Base

trait Functor[A] {
  def apply[B](b : B)(implicit mb : Manifest[B]) : A
}

case class Checker[A](func : A => String)(implicit manifest : Manifest[A]) extends Functor[String]{
  def apply[B](b : B)(implicit mb : Manifest[B]) = {
    if (mb == manifest) func(b.asInstanceOf[A])
    else "error"
  }
}

object Main{
  def fooFunc(f : Foo) = "good"

  def main(arg : Array[String]) {
    val check = Checker(fooFunc)

    println(check(new Foo) + ", " + check(new Bar))
  }
}

我仍然希望听到有人知道他们在做什么的建议。

答案 1 :(得分:2)

是的,你发现自己处于擦除之地。

在第一种情况(原始代码)中,编译器知道AFoo,但在运行时,类型参数被擦除为上限类型,即Base在此示例中(如果未指定上限类型,则type参数将被删除为Object)。所以JVM看到你的代码如下(注意没有类型参数化):

def checker(func: Base => String): Base => String =
    (b: Base) => b match {
      case a : Base => func(a)
      case _ => "error"
    }

任何FooBar对象都会匹配Base,然后JVM会尝试将其转换为Foo并致电func。如果b是类Foo的对象,则会起作用,但会抛出Bar的强制转换异常。没有惊喜。

在第二种情况下,您删除了type参数,并在A的定义中将Foo替换为checker。所以你的代码在运行时看起来像这样(函数不是类型参数化的开头,所以没有删除):

def checker(func: Foo => String): Base => String =
    (b: Base) => b match {
      case a: Foo => func(a)
      case _ => "error"
    }

这可以按预期工作,但修复只检查Foo。因此,您需要为checker和您要测试的任何其他类型单独编写Bar

在第三种情况下,保留type参数但省略function参数并将func(a)替换为"good"。这会产生与案例1相似的代码(再次从Foo删除到Base),但不会调用func,因此不需要转换为Foo 。因此,也不例外。

def checker: Base => String =
    (b: Base) => b match {
      case a: Base => "good"
      case _ => "error"
    }

您传递的任何对象(Base的子类)都与第一个子句匹配,checker始终返回“”good“'。

现在的解决方案。你使用Manifest走在了正确的轨道上(但是你展示的代码对于你想要达到的目标来说太复杂了)。当您要求Manifest Scala编译器将其他对象传递给可在运行时用于“恢复”已擦除类型的方法/函数。下面我使用'context bound'而不是拼写为(implicit manifest : Manifest[A]),但它是相同的,只是更短。

  def checker[A <: Base: Manifest](func: A => String): Base => String =
    (b: Base) => if(manifest[A].erasure == b.getClass) func(b.asInstanceOf[A])
                  else "error"

那么当你这样称呼时:

  def fooFunc(f: Foo) = "It's a foo"
  def barFunc(f: Bar) = "It's a bar"

  def main(arg: Array[String]) {
    val check1 = checker(fooFunc)
    val check2 = checker(barFunc)
    println(check1(new Foo) + ", " + check1(new Bar))
    println(check2(new Foo) + ", " + check2(new Bar))
  }

您将按预期获得输出:

It's a foo, error
error, It's a bar

Erasure是Scala各种乐趣的源泉,因为这里的类型参数化比Java更常见。没办法,我建议你可以学习一切。

答案 2 :(得分:1)

如你所定义的那样,checker只能接受一个带有Foo的函数。

如果你使fooFunc也是通用的,那么它应该可以工作:

def fooFunc[A <: Base](f : A) = "It's a foo"

但是fooFunc不是一个合适的名字,因为它可以返回任何派生自Base的东西。

def baseFunc[A <: Base](f : A) = "It's a "+f.getClass

可能就是你要找的东西

修改

class Base
class Foo extends Base
class Bar extends Base

  def checker[A <: Base]( func : A => String) : Base => String =
    (b : Base) => b match {
      case a : A => func(a)
      case _ => "error"
    }

  def fooFunc[A <: Base](f : A) = "It's a "+f.getClass.getName

  val check = checker(fooFunc)
  println(check(new Foo) + ", " + check(new Bar))