Scala可变集合协变类型疑问——《Scala in Action》内容咨询
Hey there! Let’s unpack this confusion from Scala in Action—it’s a super important concept that trips up a lot of folks when first learning Scala’s variance rules.
First, let’s recap some key terms to set the stage:
- Covariant: A type
C[+A]means ifAis a subtype ofB, thenC[A]is a subtype ofC[B]. Immutable collections likeListare covariant because you can’t modify them—so adding elements isn’t a risk. - Invariant: A type
C[A]has no subtype relationship betweenC[A]andC[B]even ifAis a subtype ofB. This is the default for mutable collections in Scala (including the realListBuffer).
The Problem With Covariant Mutable Collections
The book’s point—"Mutable objects must be invariant"—stems from a critical type safety issue that arises if you make a mutable collection covariant. Let’s use your example and extend it to see the danger:
Suppose ListBuffer was actually covariant (ListBuffer[+A]). Your initial line works fine:
val mxs: ListBuffer[String] = ListBuffer("pants")
But since String is a subtype of AnyRef, covariance would let us do this:
// This assignment would be allowed because ListBuffer[String] <: ListBuffer[AnyRef] val bufferAnyRef: ListBuffer[AnyRef] = mxs // Now we can add an Integer (which is also an AnyRef) to the buffer bufferAnyRef.append(new Integer(42)) // Uh-oh—now our original ListBuffer[String] contains an Integer! val firstItem: String = mxs.head // Runtime ClassCastException!
This breaks type safety entirely. The compiler would let us put non-String elements into a buffer we declared as holding only Strings, leading to a crash at runtime.
Why Immutable Collections Can Be Covariant
Contrast this with immutable collections like List. If you have a List[String], you can’t add elements to it—any "addition" returns a new List with the new element, and the type of that new list is correctly enforced. So covariance is safe here because there’s no way to modify the original collection’s contents.
Back to Your Example
Your specific code snippet (val mxs: ListBuffer[String] = ListBuffer("pants")) would compile just fine even if ListBuffer was covariant—this line alone doesn’t expose the problem. The issue comes when you start modifying the collection through a supertype reference, which covariance would allow.
Scala’s designers made mutable collections invariant precisely to prevent this kind of silent runtime error. That’s the core of what the book is explaining in that chapter.
内容的提问来源于stack exchange,提问作者Andrew_256

