【问题标题】:ClassCastException in a higher order generic function高阶泛型函数中的 ClassCastException
【发布时间】:2012-03-10 09:45:57
【问题描述】:

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

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)

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

这就是所谓的类型擦除吗?我对这个概念不太熟悉..
另外,我很想听听有关此问题的解决方案。

【问题讨论】:

    标签: generics scala


    【解决方案1】:

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

    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))
      }
    }
    

    我仍然想听听知道自己在做什么的人的建议。

    【讨论】:

    • 你并不真的需要 Functor 特质。清单是一个编译器技巧。编译器自动将清单对象插入到您的代码中,该对象由隐式参数拾取,该参数携带类型信息,否则将被删除。另请参阅stackoverflow.com/questions/3587286/…
    【解决方案2】:

    是的,你发现自己身处被抹去的土地。

    在第一种情况下(原始代码),编译器知道AFoo,但是在运行时类型参数被擦除到类型上限,在这个例子中是Base(如果你不指定类型上限,类型参数被擦除为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 引发强制转换异常。没有惊喜。

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

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

    这按预期工作,但它固定为仅检查 Foo。因此,您需要为 Bar 和您想要测试的任何其他类型编写单独的 checker

    在第三种情况下,您保留类型参数但省略函数参数并将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 编译器会向方法/函数传递一个附加对象,该对象可在运行时用于“恢复”已擦除的类型。下面我使用'上下文绑定'而不是拼写为(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
    

    擦除是 Scala 中各种乐趣的来源,因为这里的类型参数化比 Java 中更常见。没办法,我建议你尽可能地学习它。

    【讨论】:

      【解决方案3】:

      正如你定义的那样,检查器只能接受一个接受 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))
      

      【讨论】:

      • 不,这没有帮助。它删除了异常,但仍然为 Foo 和 Bar 打印“好”。问题似乎是检查忘记了 A 是什么类型。
      • 请问?查看更新的答案,肯定会打印 Foo & Bar。不确定您要做什么,应该用您当前使用的代码更新您的问题,这样我自己和其他人就会知道您在说什么(例如“仍然打印'好'”??)
      • 抱歉不清楚。检查器的目的是消除除一种以外的所有类型。如果结果是“Foo, Bar”,则说明 Foo 和 Bar 参数都被啤酒识别为 A。我想看到的是“Foo, error”。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-03-11
      • 2020-08-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-15
      • 1970-01-01
      相关资源
      最近更新 更多