Showing posts with label contravariance. Show all posts
Showing posts with label contravariance. Show all posts

Tuesday, 19 April 2011

Lower Bounded Wildcards in Generics

Suppose we wish to write a method called copyTo()such that it copies the data the opposite direction, i.e. from the host object to the given object:
public void copyTo(Box<E> b) {
  b.data = this.data(); 
}

The above code works fine so long as you are copying from one Box to another of exactly the same type (invariant type relationship), e.g. both are Box<Integer> or both are Box<String>. 

But operationally, b could be a Box of any type that is a superclass of E and the copying would be type safe.  This is a contravariant type relationship between Box<E> amd the input parameter to the copyTo() method where the type parameter of the object referenced by the variable b  is a superclass of E.
To express this, we write
public void copyTo(Box<? super E> b) {
  b.data = this.data();  // b.data is a superclass of this.data
}

The type parameterization <? super E> is called a "lower bounded wildcard" because it defines a type that could be any type so long as it is bounded by the subclass E.
Once again, we have greatly increased the utility of the copyTo() method because it is now not limited to only inputs of type Box<E> but will now work with any compatible Box object.
Upper and lower bounded wildcards will prove indispensable when implementing the visitor pattern on generic (parameterized) classes.

Sunday, 17 April 2011

Covariance, contravariance and invariance


In programming language conversion from narrower to broader type is of these types:
  • covariant: converting from wider (double) to narrower (float).
  • contravariant: converting from narrower (float) to wider (double).
  • invariant: Not able to convert.



Thursday, 31 March 2011

Covariant Parameter Types

Read - What is covariance?
Covariant Return type

Now we have a look at covariant method parameters, which are considered unsound.


Consider the following code:
public interface TestInterface { }

public class TestClass implements TestInterface { }

import java.util.ArrayList;
import java.util.List;

public class Test {
private List<testclass> list;

public TestInterface test() {
list = new ArrayList<testclass>();
list.add(new TestClass());

return covariant(list);
}

public TestInterface covariant(List<testinterface> ilist) {
return ilist.remove(0);
}
}

Now there is absolutely no reason why this should not work. It is trivially inferable that the above code treats ilist as covariant in the list-type - and that therefore this code is statically correct.

Of course Java's typing has never been particularly smart. List.add(T1) is contra-variant in t1, and T2 List.get(int) is co-variant in t2; so the Java compiler is correct to infer that in the general case List and List are substitutable iff t1 == t2.

If we can't declare a generic parameter to be covariant in its type parameter we have a serious problem - it means that any non-trivial algorithm involving collections is going to run afoul of this. You might consider trying to cast your way around it:


public TestInterface test() {
list = new ArrayList<testclass>();
list.add(new TestClass());

return covariant((List<testinterface>)list);
}

but not surprisingly that didn't work.

Test.java:11: inconvertible types
found : java.util.List<testclass>
required: java.util.List<testinterface>
return convariant((List<testinterface>)list);
^
1 error

If you continue to hack at it you might try a double cast via a non-generic List.

public TestInterface test() {
list = new ArrayList<testclass>();
list.add(new TestClass());

return covariant((List<testinterface>)((List)list));
}

This works but leaves us with the unchecked/unsafe operation warning:
Note: Test.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.



Now this is a perfectly reasonable warning - it is unchecked; it is unsafe; and more importantly it does violate encapsulation. The problem here is that the caller should not be defining the type invariants of the callee - that's the job of the method signature!

The correct solution is to allow us to declare covariant() to be covariant in its argument; and fortunately Java does support this.

To declare an argument to be covariant in its type parameter you can use the extends keyword:
public TestInterface covariant(List<? extends TestInterface> ilist) {
return ilist.remove(0);
}

To declare an argument to be contravariant in its type parameter you use the super keyword:
public void contravariant(List<? super TestClass> clist, TestClass c) {
clist.add(c);
}