Saturday, November 19, 2005

Re: Generics in Java

This blog post made me realize how little I really know about Generics in Java. Conceptually, I feel generics are easy to understand but when looking at these details, it becomes interesting i.e. confusing.

But having said this, I think it's important to take some perspective. Most Java programmers are 'consumers' of APIs. Using these generic classes isn't very difficult. It actually makes better type guarantees and is more natural to use (no messing with casts). The complications of generics only occur when you write generic APIs - this burden is borne by the 'producers'. This is in a way good since there are generally more consumers than producers, so fewer people have to understand the complexities of covariants, contravariants and invariants etc... When was the last time you wrote a generic class?

The other thing is that unlike C++, in Java all the checks (as you said) are done at runtime. Add to this the fact that it has a unified type system where all types (except primitives) inherit from Object makes it even more complicated. Both these are the reason for things like "? extends X" and "? super Y". You don't find these in C++ templates (Atleast I haven't seen them - can someone confirm?). Everything is checked at compile time so the compiler will warn you if the parameter types don't have expected methods. There's no need to specify that you want parameter types to extend or be the super type of some class.

I haven't delved too deep into how C# (.NET) has handled these situations. But I think there will be differences since the two implementations are different... no erasure, so type info is preserved. There is something called a constraint which basically allows you to declare an interface you want the parameter type to implement or inherit from some base class or specify that it has a default construtor...

public T genericMethod<T>() where T : constraint

where constraints can be...

where T : struct T must be a value type (a struct)
where T : class T must be reference type (a class)
where T : new() T must have a no-parameter constructor
where T : class_name T may be either class_name or one of its
sub-classes (or is below class_name
in the inheritance hierarchy)
where T : interface_name T must implement the specified interface


Why the need for constraints? Consider this simple ex.

public static T Max<T>( T a, T b )
{
    if ( a.CompareTo( b ) < 0 )
        return a;

    return b;
}


This will fail at compile time. The compiler doesn't know whether a and b support the CompareTo method. There needs to be a way to limit what the parameter types can be... some constraint.

public static T Max<T>( T a, T b ) where T : IComparable
{
    if ( a.CompareTo( b ) < 0 )
        return a;

    return b;
}


This satiates the compiler since it will make sure that when you call this method, the types of objects used as parameters implement Icomparable.

This seems like a bit of a sidetrack. I'll try and read up some more on C# generics and post any differences/similarities to covariants, contravariants and invariants.

No comments: