Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[udig-devel] Java 5 Generics + Colllections Answers x 3

I have been looking into the relationship between generics and type
narrowing. Example of are simplified from classes defined in the catalog
plugin.

Appologies for the long email; consider this notes if you are interested:
1) Use of Generics for IResolve.resolve( Class )
2) Use of bounded wild card for IResolve.members()
3) While IResolve.resolve( List<IGeoResource>.class ) will never do what
you expect

Jody out.

Here is an interesting use of Generics from the
docs(http://java.sun.com/j2se/1.5.0/docs/guide/language/generics.html):

“Holder classes,” such as |WeakReference| <http://java.sun.com/j2se/1.5.0/docs/api/java/lang/ref/WeakReference.html> and |ThreadLocal| <http://java.sun.com/j2se/1.5.0/docs/api/java/lang/ThreadLocal.html>, have all been /generified/, that is, they have been retrofitted to make use of generics. More surprisingly, class |Class| <http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Class.html> has been generified. Class literals now function as /type tokens/, providing both run-time and compile-time type information. This enables a style of static factories exemplified by the |getAnnotation| method in the new |AnnotatedElement| <http://java.sun.com/j2se/1.5.0/docs/api/java/lang/reflect/AnnotatedElement.html> interface:

<T extends Annotation> T getAnnotation(Class<T> annotationType); This is a generic method. It infers the value of its /type parameter/ |T| from its argument, and returns an appropriate instance of |T|, as illustrated by the following snippet:

   Author a = Othello.class.getAnnotation(Author.class);
As you can see this is tailor made for allowing the following:
class IResolve{
<T> T resolve( Class<T> morph ) throws IOException;
}

IResolve.resolve( Class ) is used to implement "handle" resolution for
information located on external services.

Next up is the concept of "type narrowing" ...
class IResolve {
IResolveInfo getInfo();
}
class IService extends IResolve {
IServiceInfo getInfo();\
}

This is very nice, allows the API to be explicit without producing a
host of duplicated methods, with slightly different meaning.
The problem occurs when you consider this case:

class IResolve {
List<IResolve> members();
}
class IService extends IResolve {
List<IGeoResource> members();
}

This looks like a valid type narrowing case to me, but I have been
unable to get this to compile. But of course it does not; the java
generics tutorial sheads a little light
(http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf).

In general, if Foo is a subtype (subclass or subinterface) of Bar, and G is some generic type declaration, it is not the case that G<Foo> is a subtype of G<Bar>. This is probably the hardest thing you need to learn about generics, because it goes
against our deeply held intuitions.

Well we agree with that - lets cut to the chase:

public void drawAll(List<Shape> shapes) { for (Shape s: shapes) { s.draw(this);
}
}
Now, the type rules say that drawAll() can only be called on lists of exactly Shape: it cannot, for instance, be called on a List<Circle>. That is unfortunate, since all the method does is read shapes from the list, so it could just as well be called on a List<Circle>. What we really want is for the method to accept a list of any kind of
shape:
public void drawAll(List<? extends Shape> shapes) { ... }

This makes use of a "bounded wildcard" (ie List<? extends IResolve>)
rather an "than List<?>" which is just a normal wildcard. The point
List<Object> != List<?>.

And here is our working code snipits:
class IResolve {
List<? extends IResolve> members();
}
class IService extends IResolve {
List<IGeoResource> members();
}

On a related note using the resolve method with the combination of
Collections+Generics is problamatic:

I would expect the following to work:
IService.resolve( List<IGeoResource>.class )

But the tutorial (darn them) says otherwise:

List <String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());
You might be tempted to say false, but you’d be wrong. It prints true, because all instances of a generic class have the same run-time class, regardless of their actual type
parameters.

This means that List<IGeoResource>.class == List<IResource>.class ==
List<?>.class
Can't win for trying...

For those that paid attention to the last email about surpressing
warnings - yeah I am totally wrong:
Since all the Lists classes above are the *same* class the cast is useless.

So now to our situtation:

return resolve( List.class, monitor); // this is a cast warning

List.class (with the warning) is the *best* we can do - the compiler
does someting called type erasure meaning they remove all the type
information as part of the compile process. When something cannot be
correctly simplified, they know they have a type problem, or when the
user does a cast like above.

In order to actually *do* that runtime check you need to hack:

List<IGeoResource> list = (List<IGeoResource>) resolve( List.class, monitor);
return Collections.checkedList( list, IGeoResource.class);

In general even though you can access some *possible* Types information
about the List, there is no support for figuring out exactly *which*
kind of list you are dealing with. Amazing set of tradeoffs these guys
have made (!). Willing to trade reflection & runtime checking for fairly
decent static checking.

In short this would require client code to make something that is non
obvious, and cannot scale to different sorts of lists. So members and
resolve it is.

Jody out x 2.





Back to the top