I was trying to find good examples of why contra-variance is the only variance allowed for method input parameters according to the Liskov Substitution Principle, but until now none of the examples has completely answered ,y doubts.
I was trying to build a counter-example that could prove the statement above, but I am not sure about it. Suppose we have the following classes:
class Z {...}
class X extends Z {...}
class Y extends X {...}
class A {
void m(X x);
...
}
class B extends A {
void m(Y y); // Overriding inherited method m using covariance (by contradiction)
...
}
Now, suppose I have the following situation:
B b = new B();
A a = b; // Allowed because b is also an A object (B extends A)
Now, since the static type of a
is A
, theoretically we should be able to pass X
objects to the method m
on a
(not sure what the LSP says about this):
a.m(new X());
At runtime (not sure what the LSP says about runtime and compile-time), on the other hand, this would fail, because a
is actually pointing to a B
object, but the method overridden in B
m
only accepts Y
objects, which are of subtype X
.
If we had allowed contra-variance instead, for example by overriding m
in B
by specifying the type of the parameter as Z
or X
, none of this would happen.
This is for now my (and my friend's) only explanation of why we are only allowed to use contra-variance for method parameters.
Is my explanation correct? Are there other situations that can explain this concept more in detail? Concrete examples are appreciated!