为什么从案例 class 中提取时映射键类型不起作用?

Why does not Map key type work when extracted from a case class?

在下面的代码中,映射键可以直接使用,也可以存储在val中,但存储在case class中则不能:

sealed trait FooKey
case object KeyA extends FooKey
case object KeyB extends FooKey

case class KaseKey(key:FooKey)

object Main extends App {
  val m = Map(KeyA -> "A", KeyB -> "B")


  val kk = KaseKey(KeyA)
  val kv = KeyA

  m(KeyA) // works
  m(kv) // works
  m(kk.key) // error: found: Main.kk.key.type (with underlying type FooKey)
}

最后一行显示的完整错误是:

Error:(16, 8) type mismatch;

found : Main.kk.key.type (with underlying type FooKey)

required: Product with Serializable with FooKey

这是什么原因?为什么密钥一旦存储在 case class 中就不再被接受并且无法通过类型检查?

这是因为以下行推断出密钥的类型:

val m = Map(KeyA -> "A", KeyB -> "B")

如果您查看 REPL,它会告诉您它看到 Map[Product with Serializable with FooKey,String]。这是因为 KeyAKeyB 的普通超类型就是这样。 Case 类 为您提供 Product 特征,允许迭代产品元素,定义 equalshashCode.

所以你应该在你的地图上添加注释:

val m = Map[FooKey, String](KeyA -> "A", KeyB -> "B")

或者你定义

sealed trait FooKey extends Product with Serializable

by 0__ 中所述,这是因为使用 case objects 作为映射键的结果是 m 的类型被推断为不是 FooKey,而是作为 Product with Serializable with FooKey.

可以通过使用普通 objects 而不是 case objects:

来避免这种情况
sealed trait FooKey
object KeyA extends FooKey
object KeyB extends FooKey

一个可能的缺点是使用了 default Java Object hashCode,这可能不如 case objects 提供的合理。