In Part 4 and Part 5, I introduced you with the concept of invariance and covariance respectively. In this article, I will cover contravariance.
We will use the same type hierarchies and code from the Part 5. For your convenience, I will provide the type hierarchies once again here.
Contravariance
To take advantage of covariance, we use lower bounded wildcards as follows.
MyContainer<? super EaterB> refEbContraContainer;
The expression “? super EaterB” represents type EaterB or any supertype of EaterB. Here EaterB is the lower bound. Since we are sure about the supertype EaterB, we can logically assign any subclass of EaterB to “? super EaterB”. The Figure 1 below will clarify the concept.
Contravariance means: for some generic type G, G<X> is subtype of G<? super Y> when X is a supertype of Y. For example, MyContainer<EaterA> is a subtype of MyContainer<? super EaterB>. Here, MyContainer<? super EaterB> is a contravariant instantiation of MyContainer and EaterB is the lower bound.
Inside a contravariant structure, we can put objects belonging to a particular type hierarchy where the lower bound is the root of the hierarchy.
Consider a type hierarchy where EaterB is root of the hierarchy.
The contravariant structure MyContainer<? super EaterB> will be happy to contain the elements of type EaterB or subtype of EaterB. It means we can easily add element of type EaterB, EaterC, and EaterG via refEbContraContainer. Therefore, the code below is perfectly OK.
MyContainer<? super EaterB> refEbContraContainer;
MyContainer<EaterB> refEbContainer = new MyContainer<EaterB>();
refEbContraContainer = refEbContainer;
refEbContraContainer.add(0, new EaterB());
refEbContraContainer.add(1, new EaterC());
refEbContraContainer.add(2, new EaterG());
Now we will try to retrieve objects using the reference variable refEbContraContainer. Have a look at the below code snippet.
EaterB b = refEbContraContainer.get(0); //compile-error
EaterC c = refEbContraContainer.get(1); //compile-error
EaterG g = refEbContraContainer.get(2); //compile-error
The above three lines give same kind of error. For example, Line 1 gives the below error:
Type mismatch: cannot convert from capture#8-of ? super EaterB to EaterB
Similarly, Line 2 and Line 3 give the below errors respectively.
Type mismatch: cannot convert from capture#9-of ? super EaterB to EaterC.
Type mismatch: cannot convert from capture#10-of ? super EaterB to EaterG
From the above errors, we understand that the container MyContainer<? super EaterB> can contain any elements of either type EaterB or subtype of EaterB, but we cannot say exactly which type it is. It may be EaterB or EaterC or EaterG. Therefore, we cannot assign any retrieved element from the container MyContainer<? super EaterB> to a specific type like EaterB or EaterC or EaterG. But we can assign the retrieved value to Object (since Object is the superclass of any class eventually).
Object b = refEbContraContainer.get(0);
Object c = refEbContraContainer.get(1);
Object g = refEbContraContainer.get(2);
Conclusion
We use lower bounded wildcards (? super SomeLowerBound) for contravariance. Remember, we only put data into a contravariant structure. We cannot read anything out of it apart from Object
That’s all for contravariance for the time being.
If you find any significant errors or want to give me some feedback, feel free to contact me at maliksanjoykumar[@]gmail.com.
Sanjoy Kumar Malik is an experienced software architect and technologist. He is passionate about Cloud Computing, Software Architecture, and System Design. Apart from technology and software, he is an avid LinkedIn networker. You can join his 5.5+ lacs supporters on LinkedIn.