编写一个通用的 Scala 类型转换函数

18

我正在尝试编写一个方法,将任何值转换为特定类型并返回选项,而不是像instanceOf一样抛出异常。Scala的行为与我的期望不同:

def cast[A](value: Any): Option[A] =
{
  try
  {
    Some(value.asInstanceOf[A])
  } catch
  {
    case e: Exception => None
  }
}

测试:

val stringOption: Option[String] = cast[String](2)
stringOption must beNone

出现 Error 错误

java.lang.Exception: 'Some(2)' is not None

有人知道为什么吗?


将整数值转换为字符串应该导致异常,并且该方法应返回None,但事实并非如此。我使用的是Scala 2.9.0-1。 - Bastian Echterhölter
是的,它返回Some(2)但是...不行。尝试“获取”值会抛出异常,但“getOrElse”可以。 - user166390
没错,我就是预料到那个异常会在转换方法中发生。 - Bastian Echterhölter
这可能与类型擦除有关吗? - user166390
3个回答

21

这里擦除了类型信息,因此在运行时不再知道类型A,asInstanceOf[A]编译成无操作。它只是让编译器相信结果值是类型A,但实际上在运行时并不能保证。

你可以使用Scala的'manifests'来解决这个问题。不过,JVM对基本类型/装箱的处理会强制我们做一些额外的工作。

以下代码可以工作,但它不能处理类型之间的“弱一致性”,例如Int不能被视为Long,因此cast[Long](42)返回None

def cast[A : Manifest](value: Any): Option[A] = {
  val erasure = manifest[A] match {
    case Manifest.Byte => classOf[java.lang.Byte]
    case Manifest.Short => classOf[java.lang.Short]
    case Manifest.Char => classOf[java.lang.Character]
    case Manifest.Long => classOf[java.lang.Long]
    case Manifest.Float => classOf[java.lang.Float]
    case Manifest.Double => classOf[java.lang.Double]
    case Manifest.Boolean => classOf[java.lang.Boolean]
    case Manifest.Int => classOf[java.lang.Integer]
    case m => m.erasure
  }
  if(erasure.isInstance(value)) Some(value.asInstanceOf[A]) else None
}

这并不是真的 - 对于非原始类型和泛型,asInstanceOf 总是编译成 checkcast 操作。 - dk14

5
这是因为类型擦除。在运行时,Option[A] 中的 A 是未知的,所以您可以将 Some(3) 存储在类型为 Option[String] 的变量中。
当访问选项内部的值时,会出现异常:
scala> val result = cast[String](2)
result: Option[String] = Some(2)

scala> result.get
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
        at .<init>(<console>:10)
        at .<clinit>(<console>)
        // ...

为什么 getOrElse(42) 不会生成异常而是计算结果为2? - user166390
2
Option[A].getOrElse[B] 的返回类型必须是 AB 的超类型。对于 StringInt,返回类型是 Any,你当然可以将 2 强制转换为 Any。如果你尝试使用 getOrElse("42"),你会得到一个 ClassCastException,因为返回类型将是 String - Ben James
有道理,所以没有一种通用的简单方法来编写这样的方法? - Bastian Echterhölter
我本来想回复“你可能可以通过清单文件来实现这个功能” - 好吧,看看Ruediger的答案 :) - Ben James

2

我刚刚也做了类似的事情,使用Scala 2.10、TypeTags(由于类型擦除)和scalaz的ValidationNEL:

import scala.reflect.runtime.universe._

def as[T: TypeTag](term: Any): ValidationNEL[String, T] =
  if (reflect.runtime.currentMirror.reflect(term).symbol.toType <:< typeOf[T])
    term.asInstanceOf[T].successNel[String]
  else
    ("Cast error: " + term + " to " + typeOf[T]).failNel[T]

使用选项而不是验证,代码如下:

使用选项而不是验证,代码如下:

  def as[T: TypeTag](term: Any): Option[T] =
  if (reflect.runtime.currentMirror.reflect(term).symbol.toType <:< typeOf[T])
    Some(term.asInstanceOf[T])
  else
    None

我在这里获取了信息:如何知道一个对象是否是TypeTag类型的实例?使用scala 2.10反射运行时解析类型参数


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接