加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

泛型 – 更高阶泛型函数中的ClassCastException

发布时间:2020-12-16 18:11:22 所属栏目:安全 来源:网络整理
导读:我有一个代码尝试将函数包装在另一个执行动态类型检查的函数中: class Baseclass Foo extends Baseclass Bar extends Baseobject Main{ def checker[A : Base]( func : A = String) : Base = String = (b : Base) = b match { case a : A = func(a) case _
我有一个代码尝试将函数包装在另一个执行动态类型检查的函数中:

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,则效果很好.但是,如果我保留了type参数但省略了function参数并将func(a)替换为“good”,那么我对Foo和Bar都会“好”.

这是什么叫做类型擦除?我不太熟悉这个概念..
此外,我很想听到解决方案.

解决方法

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

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

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

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

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

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

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

在第三种情况下,保留类型参数但省略函数参数并将func(a)替换为“good”.这导致类似于情况1的代码(再次从Foo擦除到Base),但是没有调用func,因此不需要转换为Foo.因此,也不例外.

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

您传递的任何对象(Base的子类)都与第一个子句匹配,并且检查器始终返回“”good“’.

现在的解决方案.你使用Manifest处于正确的轨道上(但你展示的代码对于你想要达到的目标来说太复杂了).当您要求Manifest Scala编译器将其他对象传递给可在运行时用于“恢复”已擦除类型的方法/函数.下面我使用’context bound’而不是拼写出来(隐式清单: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更常见.没办法,我建议你可以学习一切.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读