Scala的类型检查的奇怪失败



我的应用程序要求需要一个参数提供程序trait,该提供程序可以添加到任何class,以允许传递任意数量的任何类型的参数

trait Arg
case class NamedArg(key: String, value: Any) extends Arg
// I extend my classes with this trait
trait ArgsProvider {
val args: Seq[Arg]
lazy val namedArgs: Map[String, Any] = {
args.filter(_.isInstanceOf[NamedArg]).
map(_.asInstanceOf[NamedArg]).
map(arg => arg.key -> arg.value).toMap
}
...
}

然后我可以使用他们的keyArgsProviderargs中提取NamedArg,如下所示

trait ArgsProvider {
...
/*
* Method that takes in a [T: ClassTag] and a (key: String) argument
* (i) if key exists in namedArgs: Map[String, Any]
*     - Returns Some(value: T) if value can be casted into T type
*     - Throws Exception if value can't be casted into T type
* (ii) if key doesn't exist in namedArgs
*    Returns None
*/
def getOptionalTypedArg[T: ClassTag](key: String): Option[T] = {
namedArgs.get(key).map { arg: Any =>
try {
arg.asInstanceOf[T]
} catch {
case _: Throwable => throw new Exception(key)
}
}
}
...
}

尽管它可能看起来非常不直观冗长,但这种设计对我来说完美无。然而,在编写某些unit tests时,我最近发现了其中的一个主要漏洞它无法执行type-checking。(或者至少我是这么推断的)


更具体地说,当我尝试将提供的argtype-cast错误的类型时,它不会引发任何异常。例如:

// here (args: Seq[NamedArg]) overrides the (args: Seq[Arg]) member of ArgsProvider
case class DummyArgsProvider(args: Seq[NamedArg]) extends ArgsProvider
// instantiate a new DummyArgsProvider with a single NamedArg having a String payload (value)
val dummyArgsProvider: DummyArgsProvider = DummyArgsProvider(Seq(
NamedArg("key-string-arg", "value-string-arg")
))
// try to read the String-valued argument as Long
val optLong: Option[Long] = dummyArgsProvider.getOptionalTypedArg[Long]("key-string-arg")

虽然人们会期望上面的代码throwException;但令我沮丧的是,它工作得很好,并返回以下输出(ScalaREPL

optLong: option[long] = Some(value-string-arg)


我的问题是:

  • 为什么类型检查在这里失败?
  • 一般来说,Scala 的类型检查在什么情况下会失败?
  • 这种设计可以改进吗?

我正在使用

  • Scala 2.11.11
  • SBT 1.0.3

正如@AlexeyRomanov所说,as/isInstanceOf[T]不使用ClassTag

您可以改用模式匹配,如果有可用的模式匹配,它会检查ClassTag

trait ArgsProvider {
/* ... */
def getOptionalTypedArg[T: ClassTag](key: String): Option[T] = {
namedArgs.get(key).map {
case arg: T => arg
case _ => throw new Exception(key)
}
}
}

或者您可以直接使用ClassTag方法:

import scala.reflect.classTag
def getOptionalTypedArg[T: ClassTag](key: String): Option[T] = {
namedArgs.get(key).map { arg =>
classTag[T].unapply(arg).getOrElse(throw new Exception(key))
}
}

您在类型擦除方面遇到了问题:Option[Long]实际上存储字符串"value-string-arg",并不关心其被擦除的类型。

但是,如果您这样做optLong.get它将尝试将其转换为 Long,这是预期的输出。你会得到ClassCastException。


只是一点评论:

取代

val namedArgs: Map[String, Any] = {...}

val namedArgs: Map[String, Any] = args.collect{
case NameArg(k, v) => k -> v
}(collection.breakout)

另外,在你的getOptionalTypedArg中,不要捕获所有的Throwable。这是不好的做法(您可能会捕获 OutOfMemoryError 和其他致命错误,您不应该这样做)。在你的例子中,你想捕获一个ClassCastException。在其他情况下,如果您不知道确切的可投掷对象,请尝试使用非致命

相关内容

  • 没有找到相关文章

最新更新