Sunday, June 25, 2006

More generics puzzles

This week I had a couple of interesting discussions with people about some particular points of programming with Java generics. The first case was that a developer wanted to write a method that processed a list of objects that implemented two interfaces. He wasn't concerned with the actual class of the objects, just that they implemented the two interfaces in question. This seemed to be a case for the use of wildcards, where we can specify a method as taking a paramater that is, say, a List<? extends A>. However, it doesn't seem to be possible to write something like List<? extends A, B>, even when A and B are interfaces rather than classes.

Some might say that a method should only act on an object through one of its interfaces, and that any processing using the other interface should be encapsulated in a separate method, but this is a matter of design, and it does not seem that it should be being enforced by the type system. Also, it seems unlikely that this is the reason that Java does not support this.

In the end, the best solution we could come up with was to create an interface C that extended A and B, and to make the objects to be processed implement this interface instead. The source of these classes happened to be under our control so we could do this, but it didn't seem like an optimal solution, and there are many cases where it could not be used.

The second case, was where a different developer was writing a unit test using jMock mock objects for the collaborators of the class under test. In this case, this meant that he needed to mock a generic class, as the class under test had a setter method that takes a particular instantiation of the generic. So we had code along the lines of:

class X {
setK(K<String> k) {
...
}
}

class TestX extends MockObjectTestCase {
void test() {
X x = new X();
Mock mockK = mock(K.class);
x.setK(mockK.proxy());
}
}

The first thing to notice is that we cannot write mock(K<String>.class). See an earlier post for an explanation of why not. Therefore we have to mock K.class. First of all, we wondered whether this would work, especially as we were using the cglib variant of the jMock library, but in fact it did. The interesting part is that in the above code, we have to cast the result of mockK.proxy() in order for it to compile. The most obvious thing is to cast it to a K<String>. But, using Eclipse, we get a warning that says that our cast is "actually checking against the erased type". This is true, as after compilation the generic type information is not present, so there is no way to check this cast at runtime.

If we make the cast a bit less specific, and just cast to a K, then we get a different warning, saying that we need an unchecked conversion to conform to the signature of the method that we are calling. It seems there is no way we can win, and the only way to keep Eclipse happy is to add an annotation to the method to suppress the warnings - not ideal.

To some extent, these problems were caused by the fact that jMock is written using the Java 1.4 API, and so does not have inbuilt support for generics, but this is not the only cause, and it seems to be a common case that generic code is interwoven with older, non-generic code. These sorts of examples certainly show that the Java generics, and the supporting type rules, can be somewhat puzzling.





<< Home

This page is powered by Blogger. Isn't yours?