JAVA VIBES - Solid Code Design – Part 5 – Understanding Covariance

JAVA VIBES - Solid Code Design – Part 5 – Understanding Covariance

Solid Code Design – Part 5 – Advanced Java Generics

In Part 4, I introduced you with the concept of invariance. In this article, I will cover covariance.

I will use the below type hierarchies for our discussion.

Get Started

To start, I will introduce you with the code for the above type hierarchies as given below.

I will also introduce with the below generic class.

For our convenience, I will summarize the public methods of the generic class MyContainer<T> as follows:

Now we will have a quick look at usage of MyContainer<T> generic class. Also, we will try to visualize the same through memory and object diagram as follows.

We will have a look at another usage of MyContainer<T> as follows:

One more example of usage of MyContainer<T>:

Since EaterB and EaterC are subtypes of EaterA, we can add objects of types EaterB and EaterC in refEaContainer2 (See Figure C). From this observation, we may think that MyContainer<EaterB> is a subtype of MyContainer<EaterA> and MyContainer<EaterC> is a subtype of MyContainer<EaterA>.

But it is not. Therefore, we will get error “cannot convert from MyContainer<EaterB> to MyContainer<EaterA>” if we try to assign refEbContainer to refEaContainer (See Figure D).

Now question is: Can’t we build a subtyping relationship between two parameterized types of a generic class?

Yes, we can. For that purpose, we need the help of variance. Variance helps us build a subtyping relationship between parameterized types.

According to Cambridge Dictionary (online version), one of the meanings of variance is as follows:

According to Merriam-Webster Dictionary (online version), one of the meanings of variance is as follows:

Basically, when we talk about variance, we basically mean the state of being variant. When something is variant, it allows variation of a particular thing. In current context, we apply the notion of variance at the type parameter level. Moreover, variance follows few rules. Rules will depend on the type of the variance. The two most important types of variance are covariance and contravariance. In this article, we will briefly cover the core idea of covariance.

Covariance

To take advantage of covariance, we use upper bounded wildcards as follows.

Here, MyContainer<? extends EaterA> is a covariant instantiation of MyContainer. The expression “? extends EaterA“ is an upper bounded wildcard where EaterA is the upper bound. Moreover, this expression represents a type that is either EaterA or any subtype of EaterA. In turn, parameterized type MyContainer<? extends EaterA> works as a supertype of all parameterized types MyContainer<S> or MyContainer<? extends S> where S is a subtype of EaterA.

One important point to note here is that we didn’t tell anything about variance (at the type parameter level) when we declared the generic type MyContainer<T>. Instead, we define the variance through variance annotation(? extends) during the usage of a generic class (i.e., during instantiation) in Java. We call it use-site variance.

What is going on when we are using covariance?

To answer this question, we will consider a simple example for better understanding.

The MyGeneric<T> declaration has two public methods. Any invariant instantiations of MyGeneric<T> will be able to access those two public methods. To understand this, consider the below code snippet and Figure G. In the Figure G, you think about the interface conceptually.

Now consider the below code snippet.

The covariant instantiation put restrictions on accessibility of members of the corresponding generic class. The method call refMga2.set(…) will give compilation error saying “the method set(capture#2-of ? extends A) in the type MyGeneric<capture#2-of ? extends A> is not applicable for the arguments (B)“. It means compiler is expecting some other subtype of A, but we are providing type B. Basically, we won’t be able to add anything to a covariant structure.

Now consider the below code snippet:

In the above code, the reference variable?refMga2?is sometimes attached with the “MyContainer of B“; other times, it is attached with the “MyContainer of C”?and so on. Basically, compiler cannot ensure the exact type of the data residing inside the MyContainer through the reference variable refMga2. Therefore, compiler doesn’t allow us to add safely in this structure. But compiler can ensure one thing is that the type of data inside this structure (MyGeneric<? extends A>) is subtype of A. Therefore, we can get data from this structure through some public method like get and assign the same to any reference variable of type A (look at line numbers 5 and 11) because type of data inside this structure (MyGeneric<? extends A>) is a subtype of A. So, subtype polymorphism does the magic through substitution principle.

Conclusion

We use upper bounded wildcards (? extends SomeUpperBound) for covariance. Remember, we only get data out of a covariant structure. We cannot put anything in it.

That’s all for covariance for the time being.

In next article, I will discuss about contravariance.

Imad Damaj

was !!! small businesses investor

1 年

Speechless

回复
Louren?o Dias Silva

Ph.D. Gest?o Global l CBS l ESGCS

1 年

Excellent

CHESTER SWANSON SR.

Realtor Associate @ Next Trend Realty LLC | HAR REALTOR, IRS Tax Preparer

1 年

Thanks for Sharing.

要查看或添加评论,请登录

Sanjoy Kumar Malik .的更多文章

  • AI Agents Are Skilled Digital Employees

    AI Agents Are Skilled Digital Employees

    In today’s rapidly evolving technological landscape, Artificial Intelligence (AI) is transforming industries…

    8 条评论
  • Importance of APIs in Agentic AI

    Importance of APIs in Agentic AI

    Without Application Programming Interfaces (APIs), the lifeblood of interoperability, AI agents would be marooned on…

    13 条评论
  • Crafting Compelling Domain Stories in DDD

    Crafting Compelling Domain Stories in DDD

    In Domain-Driven Design (DDD), domain stories are a powerful way to capture and communicate the domain knowledge and…

    7 条评论
  • A Brief Note on Software Architecture Decisions

    A Brief Note on Software Architecture Decisions

    Architecture decisions are choices made during the design and development of a system that have a significant impact on…

    5 条评论
  • Simple Explanation - Architecture Principle

    Simple Explanation - Architecture Principle

    If you are new to software architecture or planning to enter the field of software architecture, this article will…

    3 条评论
  • Scaling of Read Operations Using Elasticsearch

    Scaling of Read Operations Using Elasticsearch

    You can use the below architecture for highly scalable read operations that are independent of the write operations…

  • Cultivating an Innovation Ethos within your Software Architecture Practice

    Cultivating an Innovation Ethos within your Software Architecture Practice

    An Innovation Ethos in your Software Architecture Practice means cultivating a culture that encourages continuous…

    4 条评论
  • Uploading Large File (Say, 1 TB size) to AWS S3

    Uploading Large File (Say, 1 TB size) to AWS S3

    Use AWS S3 Multipart Upload AWS S3 supports Multipart Uploads, which break a large file into smaller parts and upload…

    2 条评论
  • Fault-Tolerance and High Availability on AWS

    Fault-Tolerance and High Availability on AWS

    Leverage Multiple Availability Zones (AZs) Multiple AZs for Redundancy: Deploy resources across at least two…

    3 条评论
  • Achieving Scalability on AWS Cloud

    Achieving Scalability on AWS Cloud

    If you are working on achieving scalability of your application on AWS Cloud, focus on the below important areas. Auto…

社区洞察

其他会员也浏览了