Over the last year, I’ve ran into a number of cases where refactoring led to a generic abstract base class that accepted its parameter type class in its constructor. Something like this-
public abstract class BaseClass<T> {<br />
private final Class<T> type;<br />
protected BaseClass(Class<T> type) {<br />
this.type = type;<br />
}<br />
}</p>
<p>public class ConcreteClass extends BaseClass<String> {<br />
public ConcreteClass() {<br />
super(String.class);<br />
}<br />
}
Note the duplication of the type in the generic parameter declaration and in the call to the superclass constructor. Sadly, this is the usual workaround pattern.
Until Guava, finding the parameter type of a generic wasn’t easy because of type erasure at runtime it required a decent chunk of reflection code and loops to walk the class hierarchy. (Yes, TypeTools etc have similar functionality, but I prefer a minimal set of dependencies and already use Guava everywhere.) With Guava, this becomes an easy two lines of code:
<br />
public abstract class BaseClass<T> {<br />
private final TypeToken typeToken = new TypeToken(getClass()) { };<br />
private final Type type = typeToken.getType(); // or getRawType() to return Class<? super T><br />
}
No duplication, no constructor args — much simpler!
Example 1 - AbstractInjectableProvider for RESTful services with Jersey
This code has gone through a few revisions. It originally passed the Type in the constructor, as outlined above and in this Injection Providers article. Then I decided to be clever and use reflection to get the Super Type Tokens. Except, because the actual type parameter can be anywhere in the hierarchy, you have to traverse all the subclasses like so
// AbstractInjectableProvider.java
package com.mydomain.myapp.resources;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import javax.ws.rs.core.Context;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.server.impl.inject.AbstractHttpContextInjectable;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
/**
* Abstract class for making Jersey injectable providers.
*
* @param <T> the type of the injectable value.
* @author codyaray
* @since 2/01/2012
* @see http://codahale.com/what-makes-jersey-interesting-injection-providers/
*/
public abstract class AbstractInjectableProvider<T> extends AbstractHttpContextInjectable<T>
implements InjectableProvider<Context, Type> {
// The type of T
private final Type type;
public AbstractInjectableProvider() {
/*
* Use reflection to get the "Super Type Token"
* See http://gafter.blogspot.com/2006/12/super-type-tokens.html
*/
// Because this class is abstract, getClass() must return a sub-class of it.
// Traverse the hierarchy from the instantiated subclass to a direct subclass of this one
Class<?> klass = getClass();
while (!klass.getSuperclass().equals(AbstractInjectableProvider.class)) {
klass = klass.getSuperclass();
}
// This operation is safe. Because klass is a direct sub-class, getGenericSuperclass() will
// always return the Type of this class. Because this class is parameterized, the cast is safe
ParameterizedType superclass = (ParameterizedType) klass.getGenericSuperclass();
this.type = superclass.getActualTypeArguments()[0];
}
@Override
public Injectable<T> getInjectable(ComponentContext componentContext, Context context, Type type) {
return type.equals(this.type) ? getInjectable(componentContext, context) : null;
}
protected Injectable<T> getInjectable(ComponentContext componentContext, Context context) {
return this;
}
@Override
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}
}
As you can see, this is quite complex and messy. Simplify it using Guava.
// AbstractInjectableProvider.java
package com.mydomain.myapp.resources;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import javax.ws.rs.core.Context;
import com.google.common.reflect.TypeToken;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.server.impl.inject.AbstractHttpContextInjectable;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
/**
* Abstract class for making Jersey injectable providers.
*
* @param <T> the type of the injectable value.
* @author codyaray
* @since 2/01/2012
* @see http://codahale.com/what-makes-jersey-interesting-injection-providers/
*/
public abstract class AbstractInjectableProvider<T> extends AbstractHttpContextInjectable<T>
implements InjectableProvider<Context, Type> {
@SuppressWarnings("serial")
private final TypeToken<T> typeToken = new TypeToken<T>(getClass()) { };
private final Type type = typeToken.getType();
@Override
public Injectable<T> getInjectable(ComponentContext componentContext, Context context, Type type) {
return type.equals(this.type) ? getInjectable(componentContext, context) : null;
}
protected Injectable<T> getInjectable(ComponentContext componentContext, Context context) {
return this;
}
@Override
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}
}
Example 2 - AbstractXmlReader for RESTful web services with Jersey
I suspect anybody using Jersey could use something like this.
// AbstractXmlReader.java
package com.mydomain.myapp.resources.xml;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.xml.parsers.DocumentBuilder;
import com.google.common.base.Throwables;
import com.google.common.reflect.TypeToken;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
/**
* Base class for Jersey XML readers.
*
* @param <T> the type of the domain object
* @author codyaray
* @since 7/15/12
*/
public abstract class AbstractXmlReader<T> implements MessageBodyReader<T> {
@SuppressWarnings("serial")
private final TypeToken<T> typeToken = new TypeToken<T>(getClass()) { };
private final Type type = typeToken.getType();
private final DocumentBuilder builder;
protected AbstractXmlReader(DocumentBuilder builder) {
this.builder = builder;
}
@Override
public T readFrom(Class<T> type, Type genericType, Annotation[] annotations,
MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
throws IOException, WebApplicationException {
try {
return fromXml(builder.parse(entityStream).getDocumentElement());
} catch (SAXException e) {
throw Throwables.propogate(e);
}
}
@Override
public boolean isReadable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return MediaType.TEXT_XML_TYPE.equals(mediaType) && this.type.equals(genericType);
}
/**
* Subclasses implement this method to read the domain object from the xml dom.
*
* @param documentElement root document element
* @return domain object
* @throws org.xml.sax.SAXException
*/
protected abstract T fromXml(Element documentElement) throws SAXException;
}
Wooiee! What else can you simplify using Guava to get parameterized Type Tokens?
2 comments
Imported from the previous site.