在摆弄涉及使用如下所示的Any-typed值的Map的片段时,我遇到了一些类型推断不一致问题:
import reflect.runtime.universe.TypeTag
case class AnyValMap[K]( m: Map[(K, TypeTag[_]), Any] ) extends AnyVal {
def add[V](k: K, v: V)(implicit tag: TypeTag[V]) = this.copy(
m = this.m + ((k, tag) -> v)
)
def grab[V](k: K)(implicit tag: TypeTag[V]): V = m((k, tag)).asInstanceOf[V]
}
val avMap = AnyValMap[String](Map.empty).
add("a", 100).
add("b", "xyz").
add("c", 5.0).
add("d", List(1, 2, 3))
// avMap: AnyValMap[String] = AnyValMap( Map(
// (a,TypeTag[Int]) -> 100, (b,TypeTag[String]) -> xyz, (c,TypeTag[Double]) -> 5.0,
// (d,TypeTag[List[Int]]) -> List(1, 2, 3)
// ) )
avMap.grab[Int]("a")
// res1: Int = 100
avMap.grab[String]("b")
// java.util.NoSuchElementException: key not found: (b,TypeTag[String]) ...
avMap.grab[Double]("c")
// res3: Double = 5.0
avMap.grab[List[Int]]("d")
// java.util.NoSuchElementException: key not found: (d,TypeTag[scala.List[Int]]) ...
现在,如果我在add[V]
中使用显式类型信息汇编了地图,那么事情就可以了:
val avMap = AnyValMap[String](Map.empty).
add[Int]("a", 100).
add[String]("b", "xyz").
add[Double]("c", 5.0).
add[List[Int]]("d", List(1, 2, 3))
avMap.grab[Int]("a")
// res5: Int = 100
avMap.grab[String]("b")
// res6: String = xyz
avMap.grab[Double]("c")
// res7: Double = 5.0
avMap.grab[List[Int]]("d")
// res8: List[Int] = List(1, 2, 3)
我的问题:avMap
以前一种方式聚集:
avMap
似乎已捕获相应类型标记中的所有推断类型。为什么有些人没有查找?Int/Double
与String/List[T]
之间查找类型的地图值时出现不一致的情况?也许,这与TypeTag如何处理AnyVal
与AnyRef
下的类型?我正在使用Scala 2.11.12(和2.12.x似乎表现出相同的不一致性)。提前谢谢。
答案 0 :(得分:2)
致电avMap.add(List(1, 2, 3))
时的推断类型为scala.collection.immutable.List[Int]
。但是当您拨打avMap.grab[List[Int]]
时,会使用类型scala.List[Int]
。此scala.List
是package object scala
中定义的别名。
Scala了解这些类型是等效的:
scala> typeOf[scala.collection.immutable.List[Int]] =:= typeOf[scala.List[Int]]
res1: Boolean = true
但是从TypeTag
的角度来看它们仍然是不同的类型,并且它们的类型和类型标签具有不同的哈希码,因此它们是Map
中的不同键:
scala> typeTag[scala.collection.immutable.List[Int]].hashCode()
res2: Int = 629297926
scala> typeTag[scala.List[Int]].hashCode()
res3: Int = 1684352762
同样的事情发生在String
上,java.lang.String
是scala.Predef
中定义的java.lang.String
的别名。字符串常量的类型为String
,但当您使用scala.Predef.String
类型作为类型参数时,它表示map.add(Some(10))
。
将类型存储为键的方法无论如何都非常脆弱。您还会遇到子类型问题。例如,Some[Int]
推断类型为Option[Int]
而不是预期的case class AnyValMap[K]( m: Map[K, (Type, Any)] ) extends AnyVal {
def add[V](k: K, v: V)(implicit tag: TypeTag[V]) = this.copy(
m = this.m + (k -> (tag.tpe, v))
)
def grab[V](k: K)(implicit tag: TypeTag[V]): V = {
val (tpe, value) = m(k)
if (tpe <:< tag.tpe) value.asInstanceOf[V]
else throw new NoSuchElementException(s"wrong type $tpe of value for key: $k")
}
}
。
除非您真的想在同一个密钥下存储多个不同类型的值,否则我建议您将类型存储为值的一部分,并在检索时检查它是否符合所请求的类型:
scala> val avMap = AnyValMap[String](Map.empty).add("f", Some("abc"))
scala> avMap.grab[AnyRef]("f")
res4: AnyRef = Some(abc)
scala> avMap.grab[Option[AnyRef]]("f")
res5: Option[AnyRef] = Some(abc)
scala> avMap.grab[Option[String]]("f")
res6: Option[String] = Some(abc)
scala> avMap.grab[Option[Int]]("f")
java.util.NoSuchElementException: wrong type scala.Some[java.lang.String] of value for key: f
at AnyValMap$.grab$extension(<console>:28)
... 31 elided
这很有效,并且还允许通过超类型获取值:
case class AnyValMap[K]( m: Map[K, Vector[(Type, Any)]] ) extends AnyVal {
def add[V](k: K, v: V)(implicit tag: TypeTag[V]) = this.copy(
m = this.m + (k -> (this.m.getOrElse(k, Vector.empty) :+ (tag.tpe, v)))
)
def grab[V](k: K)(implicit tag: TypeTag[V]): V = {
m(k).collectFirst {
case (tpe, value: V @unchecked) if tpe <:< tag.tpe => value
}.getOrElse(throw new NoSuchElementException(s"no suitable value for key: $k"))
}
}
scala> val avMap = AnyValMap[String](Map.empty).
add("a", List(1, 2, 3)).
add("a", Some("abc"))
scala> avMap.grab[Seq[Int]]("a")
res20: Seq[Int] = List(1, 2, 3)
scala> avMap.grab[Option[String]]("a")
res21: Option[String] = Some(abc)
scala> avMap.grab[String]("a")
java.util.NoSuchElementException: no suitable value for key: a
at AnyValMap$.$anonfun$grab$extension$1(<console>:32)
at scala.Option.getOrElse(Option.scala:121)
at AnyValMap$.grab$extension(<console>:32)
... 31 elided
如果你做希望在同一个键下有不同类型的多个值,那么你可以做的最好的事情就是对一个键的所有值的序列进行线性搜索:
{{1}}