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;


Parametrized types and Arrays : Covariance of Arrays

Array types are covariant,  if a type A is a subtype of type B, then A[] is a subtype of B[].

However we have just seen that the parameterised types are not covariant. This leads to an anomaly that cannot be resolved, so Java has the rule that arrays of parameterised types are not allowed. Otherwise we could write code that would potentially cause a ClassCastException, and the whole idea behind the generic types is to remove this possibility so long as no compiler warnings are issued.

Places where covariance of arrays may be harmful:
Case 1 : ArrayStoreException
Apple[] apples = new Apple[1];
Fruit[] fruits = apples;
fruits[0] = new Strawberry();//generates ArrayStoreException

The code indeed compiles, but the error will be raised at runtime as an ArrayStoreException. Because of this behavior of arrays, during a store operation, the Java runtime needs to check that the types are compatible. The check, obviously, also adds a performance penalty that you should be aware of.


Case 2: ClassCastException
List<String>[] wordlists = new ArrayList<String>[10];
ArrayList<Integer> ali = new ArrayList<Integer>();
ali.add( new Integer(123) );
Object[] objs = wordlists;
objs[0] = ali; // No ArrayStoreException
String s = wordlists[0].get(0); // ClassCastException

Once more, generics are safer to use and "correct" this type safety weakness of Java arrays.

In the case you're now wondering why the subtyping relation for arrays is covariant, I'll give you the answer that Java Generics and Collections give: if it was invariant, there would be no way of passing a reference to an array of objects of an unknown type (without copying every time to an Object[]) to a method such as:

void sort(Object[] o);

With the advent of generics, this characteristics of arrays is no longer necessary (as we'll see in the next part of this post) and should indeed by avoided.

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...

Wednesday, 27 October 2010

Upper Bounded Wildcards in generics

Suppose we want to write a generic method which takes a list and print it only when it contains elements subclassing one particular class. So here we need upper bound.
It is possible to set the upper bound of the wildcard like this:
List<? extends Vehicle> vehicles = new ArrayList<? extends Vehicle>();    

In this example I have specified the upper bound to be the class Vehicle. I can now define the printElements() method like this:

public void printElements(List<? extends Vehicle> elements){
for(Vehicle o : elements){
System.out.println(o);
}
}

As you can see it is now safe to cast each element in the list to a Vehicle, as it is done by the new for loop inside the method.
Furthermore, it is now safe to call the method using a List<Car> instance, provided that Car extends Vehicle. Here is an example:
List<Car> elements = new ArrayList<Car>
// ... add Car elements to the list.

printElements(elements);

But, even when using a wildcard with an upper bound it is not safe to write to the List. After all, a Car is always a Vehicle, but a Vehicle is not always a Car.

The type parameterization <? extends E> is called an "upper bounded wildcard" because it defines a type that could be any type so long as it is bounded by the superclass E. It provides covariant relationship such that the referenced object's (eg. Car ) type parameter is a subclass ofVehicle's type parameter