Dynamic subclass APIs make Java seem young again
At JavaZone 2010 I will be giving a lightning talk on APIs that use dynamic subclasses. These APIs make it possible to do things in Java that seem like pure magic. Here are some ideas of what you can get from these APIs and a look under the hood, so you really understand what’s going on.
Mockito - “the best Java library named after a summery drink”
Mocking is a hotly debated subject within testing camps. I think a lot of the arguments come against mocking (beyond overuse…) come down to the fact that the last generation of mocking libraries left a lot to be desired. A desire that has been fulfilled by Mockito.
I will leave the examples at a minimum, but here’s an example from code I actually use a lot. Mocking the servlet API:
HttpServletRequest req = mock(HttpServletRequest.class);
HttpServletResponse resp = mock(HttpServletResponse.class);
when(req.getMethod()).thenReturn("GET");
servletUnderTest.service(req, resp);
verify(resp).setContentType("text/html");
For those of you who’ve struggled with jMock or it’s ilk, you may notice the concise syntax used to specify behavior (“when(…).thenReturn()”) and the fact that you don’t have to set up expectations before exercising the code.
LambdaJ - “making Java beautiful and incomprehensible”
LambdaJ pretty much starts with the realizations that waiting for new language features like closures may prove to be a very long wait indeed. Furthermore, anonymous inner classes may technically be used to simulate closures, but they are so ugly that not even a mother could love them.
Let’s look at an example: Finding the name of every member of the Simpsons family who’s older than 8, in order of age.
In plain Java:
List[Person] tmp = new ArrayList[Person]();
for (Person p : theSimpsons) {
if (p.getAge() > 8) tmp.add(p);
}
Collections.sort(tmp, new Comparator() {
public int compare(Person a, Person b) {
return a.getAge() - b.getAge();
}
});
List[String] names = new ArrayList[String]();
for (Person p : tmp) {
names.add(p.getName());
}
System.out.println(names);
More modern languages are way ahead of Java. Here’s Ruby:
puts theSimpsons.select { |p| p.age > 8 }.sort\_by(&:age).
collect { |p| p.name }
The rather simple request is simple to express. Yay!
Java 7 may make this better.
System.out.println(theSimpsons.select(#(Person p) {
return p.getAge() > 8;
}).sort\_by(#(Person p) {
return p.getAge();
}).collect(#(Person p) {
return p.getName();
});
All in all, not a bad contender. What about LambdaJ:
System.out.println(with(theSimpsons)
.retain(having(on(Person.class).getAge(), gt(8)))
.sort(on(Person.class).getAge())
.extract(on(Person.class).getName()));
It’s not perfect, but it has one thing to recommend it: This works with standard, everyday Java, today! There’s no special compilation crap. No other language. Nothin. Just plain old Java.
(Thanks to Mario Fusco for improving the example with LamdbaJ 2.3 syntax)
What’s going on
Both LambdaJ and Mockito use a tricky sleight-of-hand: Generate a dynamic subclass which records its method invocations for later use. Creating such an API is in principle easy, although my implementation leave a lot! of details unimplemented.
So: We want invocations on the generated class to be template/mock objects to be saved:
@Test
public void shouldRecordInvocations() throws Exception {
Person template = DynSubclassDemo.on(Person.class);
assertNull(DynSubclassDemo.lastMethodCall());
template.getAge();
assertEquals("getAge",
DynSubclassDemo.lastMethodCall().getName());
}
This magic is surprisingly easy to implement with CgLib:
@SuppressWarnings("unchecked")
public static Object on(Class templateClass) {
return Enhancer.create(templateClass, new InvocationHandler() {
@Override
public Object invoke(Object o, Method m, Object[] a) {
lastMethodCall = m;
return null;
}
});
}
This creates a subclass of the template class (in this case: Person). But every method in Person is overridden by calling the invocation handler. Which simply saves what method was called.
This makes it possible to implement a prototype of LambdaJ’s collect
method:
@Test
public void shouldCallRecordedMethodForEveryObject() {
names = DynSubclassDemo.collect(theSimpsons,
DynSubclassDemo.on(Person.class).getName());
assertEquals(names, asList("Lisa", "Homer", "Marge", ...));
}
public static[T ,U] List[U] collect(Iterable[T] c, U property) {
ArrayList[U] result = new ArrayList[U]();
for (T t : c) {
// An insane amount of exception handling removed
result.add((U) lastMethodCall.invoke(t));
}
return result;
}
This is a gross simplification. LambdaJ handles many aspects that my prototype simple ignores: Chained method calls, multithreading, method arguments, and multiple invocations on the same line.
The future of Java
Maybe the future of Java isn’t extending the language. Maybe the future of Java isn’t replacing the language with Scala. Maybe the future of Java is smart people discovering smarter ways of using what’s already there. My hat off to Mario Fusco, Szczepan Faber and everybody who’s contributed to LambdaJ or Mockito.
Come to my talk at JavaZone to learn more!
Comments:
[Mario Fusco] - Sep 7, 2010
Hi Johannes,
I am Mario Fusco. First of all thanks a lot for your interest in lambdaj.
As for your examples maybe it worth to mention that you could make some of them more readable by using the lambdaj fluent interface collections, introduced with the release 2.3, as it follows:
with(theSimpsons) .retain(having(on(Person.class).getAge(), gt(8))) .sort(on(Person.class).getAge()) .extract(on(Person.class).getName());
Do you think it makes sense?
Cheers, Mario
Johannes Brodwall - Sep 8, 2010
Hi, Mario
Thanks a lot for the comment, for LambdaJ and for the suggested improvement. I’ve incorporated it in the article.
I like the fluent interface collections. Putting the “startpoint” in the middle, instead of at the beginning of the expression, bothered me about the examples.
By the way, how do you like my suggestion for a slogan for LambdaJ?
Johannes Brodwall - Sep 16, 2010
Thank you for the nice comment and thanks for pointing out the problem with compare(). I’ve fixed the code now.
The Guava code is nicer than the pure Java-code, but I think I’ll always prefer a straight up foreach-loop over anonymous inner classes.
[Bjorn] - Sep 16, 2010
I saw your talk at Javazone, very nice :) Thought I’d give a quick-and-slightly-hackish version of this example using Google Guava
List theSimpsons = Lists.newArrayList();
System.out.println(Joiner.on(", “).join(Ordering.from(new Comparator() { public int compare(Person a, Person b) { return a.getAge() - b.getAge(); } } }).sortedCopy(Iterables.filter(theSimpsons, new Predicate() {
public boolean apply(Person input) {
return input.getAge() > 8; } }))));
Also there is a small error in your Comparator implementation, the compare(…) signature returns int not void
NABH - May 28, 2011
I really appreciate your post and you explain each and every point very well.Thanks for sharing this information.And I’ll love to read your next post too. Regards: NABH