Showing posts with label covariance. Show all posts
Showing posts with label covariance. Show all posts

Friday, 1 July 2011

Subtyping in Generics

Before starting on subtyping in Generics, let's first understand what is covariance.
See - What is covariance, contravariance and invariance?

In Java, as in other object-oriented typed languages, hierarchies of types can be built:
 
In Java, a subtype of a type T is either a type that extends T or a type that implements T (if T is an interface) directly or indirectly. Since "being subtype of" is a transitive relation, if a type A is a subtype of B and B is a subtype of C, then A will be a subtype of C too. In the figure above:
  • FujiApple is a subtype of Apple.
  • Apple is a subtype of Fruit.
  • FujiApple is a subtype of Fruit.
Every Java type will also be subtype of Object.

Every subtype A of a type B may be assigned to a reference of type B:

Apple a = ...;
Fruit f = a;


Sunday, 17 April 2011

Making the return type of method as generic

Sometimes we may expect different return type for same function.
Consider the following method:

public static final <X> X myGenericMethod(String property)


The method returns a type of whatever you expect it to be (<X> is defined in the method and is absolutely unbounded).

Calling the method :

In this case, the compiler can guess:


Set<String> s = MyClass.myGenericMethod("Some property");

But if it can't, you must type:
MyClass.<String>myGenericMethod("Some Property");


However note that this is very, very dangerous as no provision is made that the return type actually matches the returned value.
The only advantage this has is that you don't have to cast the return value of such generic lookup methods that can return any type.
Use such constructs with care, because you loose pretty much all type-safety and gain only that you don't have to write an explicit cast at each call to get().
And yes: this pretty much is black magic that blows up at runtime and breaks the entire idea of what generics should achieve.

Also if we don't use generic parameter of method, but rather than class the above method becomes:
class MyClass<T>
{

    public static final X myGenericMethod(String property)

}

And now use parametric style only on class object, rather than function.
MyClass<T>.myGenericMethod("Some property");

This facility allows you to create methods in a subclass that return an object whose type is a subtype of that returned by the method it overrides.

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);
}



Friday, 28 January 2011

Generic types are not covariant : Generics allow you to use only one type

All the Object-based collection types (known as raw types) in classic Java are now retrofitted as generic types and accept type parameters. Generic types allow you to express your intent as being restricted to a particular type when creating and using it...

TreeSet<String> names = new TreeSet<String>();
names.add("Fred");
names.add("Wilma");
names.add("Pebbles");

SortedSet<String> is called the parameterized type and String is the actual type argument. Type-safety checking will be performed at compile time...
names.add(new Integer(100)); //compilation error

The compiler will also reject the following...
TreeSet<Object> names = new TreeSet<String>();
TreeSet<Object> alias = (TreeSet<Object>)names;


This is because generic types are not covariant – a TreeSet of String references is not a TreeSet of Object references.
With generic types, retrieved elements do not require explicit conversion...