HashMap的任何类型的伪变量很好的设计

时间:2015-08-17 19:25:39

标签: scala hashmap type-safety

我在Scala中编写简单的变量系统。我需要一种方法来很好地保存这些AnyVar并通过它们的字符串名称访问它们。我有这个对象称为上下文来管理它,但它实际上只是HashMap[String, AnyVar]的包装器。我想找到一个更好的方法来做到这一点。

class Context {
  import scala.collection.mutable.HashMap
  import scala.reflect.ClassTag

  private var variables = HashMap[String, AnyVal] ()

  def getVariable (name: String):AnyVal = variables(name)
  def setVariable (name: String, value: AnyVal) = variables += ((name, value))

  def getVariableOfType[T <: AnyVal : ClassTag] (name:String):T ={
    val v = variables(name)
    v match {
      case x: T => x
      case _ => null.asInstanceOf[T]
    }
  }
}

我真的想要一个类型安全的实现

2 个答案:

答案 0 :(得分:4)

您将变量的上限定义为AnyVal。这些大多是原始类型,例如IntBoolean等,而不是null有意义的引用类型。因此,您必须将演员表添加到T。这不会为您提供空引用,但会为您提供该值类型的默认值,例如0IntfalseBoolean

null.asInstanceOf[Int]      // 0
null.asInstanceOf[Boolean]  // false

因此,这与类型安全无关,而是Scala否则会拒绝在此处使用null值这一事实。与x: T匹配的模式已经是类型安全的。

如果您尝试查询不存在的变量或错误的类型,更好的方法是返回Option[T]或抛出NoSuchElementException

请注意,使用没有同步的普通哈希映射意味着您的Context不是线程安全的。

答案 1 :(得分:2)

使用抽象类型进行模式匹配很困难,对于AnyVal的实例更是如此。我发现了一种适用于ClassTag based pattern matching fails for primitives

的方法

一般来说,对于您的问题,我只会使用带有隐式扩展名的Map[String, AnyVal]来为您提供getOfType。然后,您可以使用现有方法根据需要放置和获取变量。您可以使用类型别名为您的类型提供更有意义的参考。

object Context {

  import scala.reflect.ClassTag
  import scala.runtime.ScalaRunTime

  type Context = Map[String, AnyVal]

  def apply(items: (String, AnyVal)*): Context = items.toMap

  implicit class ContextMethods(c: Context) {

    class MyTag[A](val t: ClassTag[A]) extends ClassTag[A] {
      override def runtimeClass = t.runtimeClass
      override def unapply(x: Any): Option[A] = 
        if (t.runtimeClass.isPrimitive && (ScalaRunTime isAnyVal x) &&
          (x.getClass getField "TYPE" get null) == t.runtimeClass)
          Some(x.asInstanceOf[A])
        else t unapply x

    }

    def getOfType[T <: AnyVal](key: String)(implicit t: ClassTag[T]): Option[T] = {
      implicit val u = new MyTag(t)
      c.get(key).flatMap {
          case x: T => Some(x: T)
          case _ => None
      }
    }
  }
}

将这些东西收集在一个可以导入的对象中,使用它变得简单明了:

import Context._

val context = Context(
  "int_var" -> 123,
  "long_var" -> 456l,
  "double_var" -> 23.5d
)

context.get("int_var")//Some(123)
context.get("long_var")//Some(456)

context.getOfType[Int]("int_var")//Some(123)
context.getOfType[Double]("double_var")//Some(23.5)
context.getOfType[Int]("double_var")//None