1

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

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
  }

  ...
}

然后我可以从使用它们中提取NamedArgs如下argsArgsProviderkey

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。(或者至少这是我推断的)


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

// 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; 令我沮丧的是,它运行良好并返回以下输出(on Scala REPL

optLong: Option[Long] = Some(value-string-arg)


我的问题是:

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

我正在使用

  • Scala 2.11.11
  • SBT 1.0.3
4

2 回答 2

3

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

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

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))
  }
}
于 2018-02-20T11:23:06.650 回答
1

你有类型擦除的问题: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。在您不确切知道哪个 Throwable 的其他情况下,请尝试使用NonFatal

于 2018-02-20T09:51:07.103 回答