为何Scala不可变HashMap值类型协变,可变HashMap却不支持?
HashMap[A, +B] is Covariant in B, but Mutable HashMap[A, B] Isn't Great question! This gets to the heart of how Scala's variance rules enforce type safety, especially when distinguishing between immutable and mutable collections. Let's break this down clearly:
First: What is Covariance?
In Scala, a type C[+T] is covariant in T—meaning if T is a subtype of U, then C[T] is a subtype of C[U]. For example, since Dog <: Animal, a List[Dog] can be treated as a List[Animal] because List is covariant in its element type.
Why Immutable HashMap Can Safely Use Covariance for B
Immutable collections have a critical property: you can never modify their contents after creation. Any operation that "changes" the collection returns a brand new copy instead.
For an immutable HashMap[A, +B], since you only read values (not write them), treating a HashMap[String, Dog] as a HashMap[String, Animal] is completely safe:
- When you retrieve a value, you get a
Dog—which is a validAnimal(sinceDogextendsAnimal). - There’s no way to add a non-
Dogvalue to the map (it’s immutable!), so the underlying type integrity stays intact.
Here’s a working example:
trait Animal case class Dog(name: String) extends Animal case class Cat(name: String) extends Animal // Immutable HashMap: covariant in B works safely val dogMap: scala.collection.immutable.HashMap[String, Dog] = scala.collection.immutable.HashMap("buddy" -> Dog("Buddy")) // We can safely upcast to HashMap[String, Animal] val animalMap: scala.collection.immutable.HashMap[String, Animal] = dogMap val pet = animalMap("buddy") // pet is of type Animal, and it's actually a Dog—no issues!
Why Mutable HashMap Can’t Be Covariant in B
Mutable collections allow in-place modifications (like put or update), which breaks covariance safety. If we allowed a mutable HashMap[A, +B], we’d risk runtime type errors that the compiler can’t catch.
Let’s imagine what would happen if mutable HashMap were covariant (this won’t compile in real Scala!):
// Hypothetical scenario (invalid in actual Scala) val mutableDogMap: scala.collection.mutable.HashMap[String, Dog] = scala.collection.mutable.HashMap("buddy" -> Dog("Buddy")) // If covariance were allowed, we could upcast to HashMap[String, Animal] val mutableAnimalMap: scala.collection.mutable.HashMap[String, Animal] = mutableDogMap // Now we can add a Cat (which is an Animal) to the map mutableAnimalMap.put("whiskers", Cat("Whiskers")) // But the original map is supposed to hold Dogs! val dog = mutableDogMap("whiskers") // Runtime error: Cat cannot be cast to Dog
This is a classic type safety violation. To prevent this, Scala makes mutable HashMap invariant in B (no + modifier), so the above code won’t even compile—keeping your code safe at build time.
Key Takeaway
- Immutable collections: Covariance is safe because you can’t modify contents—reading values preserves subtype relationships.
- Mutable collections: Covariance is unsafe because modifications can introduce invalid types, leading to runtime crashes.
内容的提问来源于stack exchange,提问作者ogen

