I read some articles about Covariance, Contravariance, and Invariance in Java, I'm confused trying to understand this :(
I am using java 11, and I have a class hierarchy A :> B :> C
(means that C is a subtype of B and A, and B is a subtype of A) and a class Container:
class Container<T> {
public final T t;
public Container(T t) {
this.t = t;
}
}
for example, if I define a function:
public Container<B> method(Container<B> param){
...
}
here is my confusion, why does the third line compile?
method(new Container<>(new A())); // ERROR
method(new Container<>(new B())); // OK
method(new Container<>(new C())); // OK Why ?, I make a correction, this compiles OK
if in Java Generics are invariant.
when I define something like this:
Container<B> conta = new Container<>(new A()); // ERROR, Its OK!
Container<B> contb = new Container<>(new B()); // OK, Its OK!
Container<B> contc = new Container<>(new C()); // Ok, why ? It's not valid, because they are invariant
can you please help me understand this?
Thanks!
CodePudding user response:
Covariance is the ability to pass or specify a subtype when a supertype is expetced. If your C class extends B, then C is a child class of B. This relationship between C and B is also called is-a
relationship, where an instance of C is also an instance of B. Therefore when your variable contc
is expecting a B instance and you're passing new C()
, since new C()
is an instance of C and C instance is (also)-an
instance of B, then the compiler allows the following writing:
Container<B> contc = new Container<>(new C());
Conversely, when you're writing
Container<B> conta = new Container<>(new A());
you're receiving an error because A is a supertype of B, there is no is-a
relationship from A to B, but rather from B to A. This is because every instance of B is also an instance of A, but not every instance of A is an instance of B (To make a silly example, every thumb is a finger but not every finger is a thumb). A is a generalization of B; therefore it cannot appear where a B instance is expected.
Here there's a good article expanding the concept of covariance in java.
https://www.baeldung.com/java-covariant-return-type
CodePudding user response:
The question's examples don't demonstrate the invariance of generics.
An example which does demonstrate this would be:
ArrayList<Object> ao = new ArrayList<String>(); // does not compile
(You might incorrectly expect the above to compile, because String
is a subclass of Object
.)
The question shows us different ways to construct Container<B>
objects - some of which compile and others which do not, because of the inheritance hierarchy of A
, B
and C
.
That diamond operator <>
means that the created container is of type B
in every case.
If you take the following example:
Container<B> contc = new Container<>(new C()); // compiles
And re-write it by populating the diamond with C
, the you will see that the following does not compile:
Container<B> contc = new Container<C>(new C()); // does not compile
That will give you the same "incompatible types" compilation error as my ArrayList
example.