Reputation: 851
I have a general question about Kotlin collections.
Why are there mutable versions of so many collections (like the MutableList
) when we have the val
vs var
distinction?
Well....ok...actually, I understand that val
doesn't have anything to do with the 'mutability' of the object, but rather the 're-initializability' of the object.
But then that raises the question....why isn't MutableList
the default?
Upvotes: 2
Views: 862
Reputation: 851
Individually, mutable and immutable collections are able to expose useful features that can't co-exist in a single interface:
List
interface for examples.Immutable collections in Kotlin cannot have elements added or removed from them; they can only be read from. But this apparent restriction makes it possible to do some subtyping with the immutable collections. From the Kotlin docs:
The read-only collection types are covariant...the collection types have the same subtyping relationship as the element types.
This means that, if a Rectangle
class is a child of a Shape
class, you can place a List<Rectangle>
object in a List<Shape>
variable whenever required:
fun stackShapes(val shapesList: List<Shape>) {
...
}
val rectangleList = listOf<Rectangle>(...)
// This is valid!
stackShapes(rectangleList)
Mutable collections, on the other hand, can be read from and written to. Because of this, no sub-typing or super-typing is possible with them. From the Kotlin docs:
...mutable collections aren't covariant; otherwise, this would lead to runtime failures. If
MutableList<Rectangle>
was a subtype ofMutableList<Shape>
, you could insert other Shape inheritors (for example, Circle) into it, thus violating its Rectangle type argument.
val rectangleList = mutableListOf<Rectangle>(...);
val shapesList: MutableList<Shape> = rectangleList // MutableList<Rectangle>-type object in MutableList<Shape>-type variable
val circle = Circle(...)
val shape: Shape = circle // Circle-type object in Shape-type variable
// Runtime Error!
shapesList.add(shape) // You're actually trying to add a Circle to a MutableList<Rectangle>
// If rectanglesList couldn't be put into a variable with type MutableList<Shape> in the first place, you would never have run into this problem.
At this point, you might be thinking: "So what? Kotlin could just add type-checks to all of the write-methods of Mutable Collections...then you could allow them to be covariant, and you wouldn't need separate immutable collections!"
Which is true, except that it would go completely against a core Kotlin philosophy; to avoid nulls
and runtime errors whenever possible. You see, the methods of such a Collection would have to return null
- or raise an Exception - whenever a type-check fails. This would only become apparent at runtime, and since that can be avoided by simply making Mutable Collections invariant...that's exactly what Kotlin does.
Upvotes: 1
Reputation: 6021
From the Kotlin docs:
The read-only collection types are covariant. This means that, if a Rectangle class inherits from Shape, you can use a
List<Rectangle>
anywhere theList<Shape>
is required. In other words, the collection types have the same subtyping relationship as the element types. Maps are covariant on the value type, but not on the key type.
In turn, mutable collections aren't covariant; otherwise, this would lead to runtime failures. If
MutableList<Rectangle>
was a subtype ofMutableList<Shape>
, you could insert other Shape inheritors (for example, Circle) into it, thus violating its Rectangle type argument.
To paraphrase, if it's immutable you know all the types are the same. If not, you might have different inheritors.
Upvotes: 0