A new JEP Candidate proposes to facilitate the handling of the recondite concept of type variance in Java. The new proposal, potentially targeting Java 10, would introduce a means for specifying default variance of target types in the definition of the generic type, as opposed to the current style of indicating it through wildcards when the generic type is instantiated. This proposal is not a replacement for wildcards, but rather a way to reduce the need for them.

The topic of type variance is still rather obscure for many developers, and the fact that in Java this problem was addressed through some rather unpopular wildcards didn’t help to make it clearer. For this reason, and to make sure our readers understand the potential impact of this JEP, in this article we will first explain what type variance is and how it is currently addressed in Java, followed by a description of what the new proposal would achieve.

Variance, Covariance and Contravariance

Consider the following code belonging to a typical online shopping application:

public class Product { /* ... */ } public class FrozenProduct extends Product { /* ... */ }

If we have a method scan(Product product) , and we call it passing a FrozenProduct object, the call works with no problem; this is well-known under the common rules of polymorphism. However, when the arguments include generics, the same logic cannot be applied, and the following code would fail to compile:

private void addAllProducts(List<Product> products) { /* Check product stock */ shoppingCart.addAll(products); } private void freezerSection() { List<FrozenProduct> frozenProducts = /* select frozen products */; addAllProducts(frozenProducts); // ERROR: List<Product> expected // List<FrozenProduct> found }

When generics are used in Java, no assumptions are made with regards to the compatibility of the target type in relation to its subtypes or supertypes. In other words, when generics are used, we say that the target type is by default invariant: only the exact type is accepted.

In our example above however, we can see that the addAllProducts method would actually work with a List of Product or of any its subtypes. When a generic argument can accept either its target type or any of its subtypes, we say that the type is covariant, which in Java is expressed using extends :

private void addAllProducts(List<? extends Product> products) { /* Check product stock */ shoppingCart.addAll(products); } private void freezerSection() { List<FrozenProduct> frozenProducts = /* select frozen products */; addAllProducts(frozenProducts); // works with no problem }

In these examples, the variance of the target type that is accpeted is the subtype. There are other scenarios where the variance of the target type is not the subtypes, but the supertypes. Consider the following case:

private boolean askQuestion(Predicate<String> p) { return p.test("hello"); } private void applyPredicate() { Predicate<Object> evenLength = o -> o.toString().length() % 2 == 0; askQuestion(evenLength); // ERROR: Predicate<String> expected // Predicate<Object> found }

In this case, we can see that applying the string "hello" to the lambda o -> o.toString().length() % 2 == 0 would work with no problem, however, the compiler is not letting us do it. askQuestion should be ok with a Predicate of String or of any of its supertypes: we say that the target type in this case is contravariant, which in Java is expressed using super :

private boolean askQuestion(Predicate<? super String> p) { return p.test("hello"); } private void applyPredicate() { Predicate<Object> evenLength = o -> o.toString().length() % 2 == 0; askQuestion(evenLength); // works with no problem }

Wildcards are a very flexible way to establish type variance since it allows you to define different variances for the same types in different places. For instance, in the example above we decided that addAllProducts should have a covariant argument, but we can make it contravariant or invariant in other places if that suits our needs. The downside however, is that one has to specify variance explicitly in every place where needed, which quickly adds a lot of verbosity and confusion. This is where the new proposal comes in.

Indicating Default Variance at Declaration-Site

One of the main problems with wildcards is that they are more flexible than developers generally need them to be. In the example of Predicate<String> , one could in theory create a method that expects Predicate<? extends String> , however, the number of uses cases where this would be useful is very limited (if there are any at all). For a large number of cases, there is only one type variance that makes sense, and to reflect this JEP 300 proposes a way to indicate the default variance at the time of declaring the generic type, as opposed to at the time of instantiation. For instance, with this proposal, the interface Predicate<T> could be rewritten using the proposed contravariant keyword as Predicate<contravariant T> , which would mean that any time a developer writes Predicate<String> , it would be implicitly interpreted as Predicate<? super String> .

The syntax for this new feature hasn’t been decided, although a few options have been suggested: using new explicit keywords, like Function<contravariant T, covariant R> , or follow the lead of other languages, like symbols in Scala ( Function<-T, +R> ), or shorter keywords in C# ( Function<in T, out R> ). Before the syntax issue gets tackled though, some important technical aspects need to be addressed, namely what the interaction of default variance and wildcards will be, the impact that default variance could have on existing code, and the actual mechanism by which compatibility of variant types will be checked.

Finally, it’s worth pointing out that JEP 300 will only deal with creating the new capability of default variance, but it won’t modify any of the classes and interfaces available in the Java library; this kind of work is expected to happen should JEP 300 go ahead, but it would be executed under a different JEP.