I've read some articles about Covariance, Contravariance, and Invariance in Java, but I'm confused about them.
I'm 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
One of the boons introduced with Java 7 is the so-called diamond operator
<>.And it has been with us for so long, that it's easy to forget that every time when diamond is being used while instantiating a generic class the compiler should infer the generic type from the context.
If we define a variable which will hold a reference to a list of
Personobjects like this:the compiler will infer the type of the
ArrayListinstance from the type of the variablepeopleon the left.In the Java language specification, the expression
new ArrayList<>()is being described as a class instance creation expression and because it doesn't specify the generic type parameter and is used within a context, it should be classified as being a poly expression. A quote from the specification:I.e. when diamond
<>is used with a generic class instantiation, the actual type will depend on the context in which it appears.The three statements below represent the case of so-called assignment context. And all three instances
Containerwill be inferred as being of typeB.Since all instances of container are of type
Band of parameter type expected by the contractor also will beB. I.e. can provide an instance ofBor any of its subtypes. Therefore, in the case1we are getting a compilation error, meanwhile2and3(Band subtype ofB) will compile correctly.And it in't a violation of invariant behavior. Think about it this way: we can store in a
List<Number>instances ofInteger,Byte,Double, etc., that would not lead to any problem since they all can represent their super typeNumber. But the compiler will not allow assigning this list to any list that is not of typeList<Number>because otherwise it would be impossible to ensure that this assignment is safe. And that is what the invariance means - we can assign onlyList<Number>to a variable of typeList<Number>(but we are free to store any subtype ofNumberin it, it's safe).As an example, let's consider that there's a setter method in the
Containerclass:Now let's use it:
When we deal with a class instance creation expression using diamond
<>, which is passed to a method as an argument, the type will be inferred from the invocation context as the quote from the specification provided above states.Because
method()expectsContainer<B>, all instances above will be inferred as being of typeB.Note
The important thing to mention that prior to Java 8 (i.e. with Java 7, because we are using diamond) the expression
new Container<>(new C())will be interpreted by the compiler as a standalone expression (i.e. the context will be ignored) creating an instance ofContainer<C>. It means your initial guess was somewhat correct: with Java 7 the below statement would not compile.But Java 8 has introduced a feature called target types and poly expressions (i.e. expressions that appear within a context) that insures that context will always be taken into account by the type inference mechanism.