Type dependency in Java, Part 2

Using covariance and contravariance in your Java programs

1 2 3 Page 3
Page 3 of 3

This flexibility ensures additional covariance: the lambda expression is upwardly compatible, even in a case were the traditional method is not.

Generic lambda expressions

Unlike their methods, functional interfaces may be generic. You saw this in the Comparator example, which is a generic functional interface. The type parameter may also be bounded, just like other generic types. We can also declare a wildcard reference, as shown:


@FunctionalInterface
interface GenericFunctionalInterface<T> {
	void method(T t);
}
...
GenericFunctionalInterface<?> wildcardReference;
wildcardReference = (String string) -> System.out.println(string);
wildcardReference = (Integer integer) -> integer++;

There is no way to use this reference, however. Any attempt to call this method fails:


wildcardReference.method("Wildcard"); // type error
wildcardReference.method(1); // type error
wildcardReference.method(new Object()); // type error

Nothing is compatible to the wildcard: no parameter we try to pass to method() will fit, not even an Object. Unfortunately, not even an upper bound for the wildcard helps:


GenericFunctionalInterface<? extends Type> covariantReference;
covariantReference = (Type parameter) -> System.out.println(parameter);
covariantReference.method(new Type(){}); // type error

The error shows that a lambda expression takes only the exact type of its parameter--there is no flexibility; there is no covariance.

The lower bound seems a bit more useful, because method() can be called with an object of the bound:


GenericFunctionalInterface<? super SubType> contravariantReference;
contravariantReference = (SuperType parameter) -> System.out.println(parameter);
contravariantReference.method(new SubType(){}); // compiles well
contravariantReference.method(new SuperType(){}); // type error

However, a supertype of the bound doesn't work anymore: the parameter must be exactly of the type of the lambda expression. In this case, it would be ?, and there is no such object, s there is no use of ? super.

Because of these rules, we can say that generic lambdas are invariant with respect to their type parameters.

In sum: Lambda expressions (just like method calls) are covariant with respect to signature, result type, and exception specification. Generic lambda types (functional interfaces) are invariant with respect to type parameter.

Conclusion

Covariance and contravariance refer to the compatibility or incompatibility of type-dependent language elements. In Java, array types are implicitly covariant and explicitly contravariant (through type conversion, or casting). Parametrized (generic) types are implicitly invariant, and this does not change with type conversion. Covariance and contravariance can be declared for parametrized types through binding the wildcard, however: we declare covariance through an upper bound (<? extends Bound>) and contravariance through a lower bound (<? super Bound>). Wildcard types are abstract, and can be used only for references.

Method calls are covariant to declarations and definitions concerning signature and result type. Method declarations and definitions are invariant among each other, concerning signature, and covariant concerning result type.

Table 3 summarizes this discussion of type dependency and variances in Java.

1 2 3 Page 3
Page 3 of 3