I had found an easy explanation in SIA. Following is straight from there.
Mutable objects need to be invariant
A type parameter is invariant when it’s neither covariant nor contravariant. All Scala mutable collection classes are invariant. An example can explain why mutable objects need to be invariant. Because ListBuffer is mutable, it’s declared as invariant as follows:
final class ListBuffer[A] ...{ ... }
Because it’s declared as invariant, you can’t assign ListBuffer from one type to another. The following code will throw a compilation error:
scala> val mxs: ListBuffer[String] = ListBuffer("pants")
mxs: scala.collection.mutable.ListBuffer[String] =
ListBuffer(pants)
scala> val everything: ListBuffer[Any] = mxs
<console>:6: error: type mismatch;
found : scala.collection.mutable.ListBuffer[String]
required: scala.collection.mutable.ListBuffer[Any]
val everything: ListBuffer[Any] = mxs
Even though String is a subtype of scala.Any, Scala still doesn’t let you assign mxs to everything. To understand why, assume ListBuffer is covariant and the following code snippet works without any compilation problem:
scala> val mxs: ListBuffer[String] = ListBuffer("pants")
mxs: scala.collection.mutable.ListBuffer[String] =
ListBuffer(pants)
scala> val everything: ListBuffer[Any] = mxs
scala> everything += 1
res4: everything.type = ListBuffer(1, pants)
Can you spot the problem? Because everything is of the type Any, you can store an integer value into a collection of strings. This is a disaster waiting to happen. It’s exactly what happens to Java arrays. To avoid these kinds of problems, it’s always a good idea to make mutable objects invariant. The next question is what happens in case of an immutable object for collections. It turns out that for immutable objects, covariance isn’t a problem at all. If you replace ListBuffer with the immutable List, you can take an instance of List[String] and assign it to List[Any] with- out a problem.
scala> val xs: List[String] = List("pants")
xs: List[String] = List(pants)
scala> val everything: List[Any] = xs
everything: List[Any] = List(pants)
The only reason this assignment is safe is because List is immutable. You can add 1 to xs List, and it will return a new List of type Any.
scala> 1 :: xs
res5: List[Any] = List(1, pants)
Again, this addition is safe because the cons(::) method always returns a new List, and its type is determined by the type of elements in the List. The only type that could store an integer value and reference value is scala.Any. This is an important property to remember about type variance when dealing with mutable/ immutable objects.
The best way to understand contravariance is to see the problem that comes when it’s absent. Try to spot the problem in the following Java code example:
Object[] arr = new int[1];
arr[0] = "Hello, there!";
You end up assigning the string to an integer array. Java catches this error at runtime by throwing an ArrayStoreException. Scala stops these kinds of errors at compile time by forcing parameter types to be either contravariant or invariant.
Hope this helps.