我的应用程序要求需要一个参数提供程序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
}
...
}
然后我可以使用他们的key
从ArgsProvider
args
中提取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
。(或者至少我是这么推断的)
更具体地说,当我尝试将提供的arg
type-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")
虽然人们会期望上面的代码throw
Exception
;但令我沮丧的是,它工作得很好,并返回以下输出(Scala
REPL
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。在其他情况下,如果您不知道确切的可投掷对象,请尝试使用非致命