Thursday, March 13, 2008

Referencing Java's "Erased" Generics

After digging into EJB 3.0 over the past few days, particularly the Java Persistence API (JPA), I encountered a curious feature in regards to Java's Generics.

From the JavaDocs for @OneToMany:

Defines a many-valued association with one-to-many multiplicity.

If the collection is defined using generics to specify the element type, the associated target entity type need not be specified; otherwise the target entity class must be specified.

Essentially, this means that the JPA implementation must be able to use the genericized type to obtain the target entity class. I never really considered this in detail before, but just assumed that such information was lost at runtime, due to type erasure.

In fact, while the genericized types are converted to raw types, the generics information is still available through reflection.

Here is the (correct and) extended example given in the JavaDoc for @OneToMany: (The referenced page doesn't currently show the generics tags due to an HTML error!)

import java.util.Set;
import javax.persistence.OneToMany;

public class Customer{

  protected Set<Order> orders;

  @OneToMany(cascade=ALL, mappedBy="customer")
  public Set<Order> getOrders() { return orders; }
  public void setOrders(Set<Order> orders) { this.orders = orders; }
}

And here is some sample code to find the "type" of Set at runtime. Given the above example, it should return Orders.class:

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public Class getEntityClass(){
  Class<Customer> cc = Customer.class;
  Method m = cc.getMethod("getOrders");
  Type t = m.getGenericReturnType();
  if(t instanceof ParameterizedType){
    ParameterizedType pt = (ParameterizedType)t;
    Type[] ata = pt.getActualTypeArguments();
    if(ata.length == 1){
      return (Class)ata[0];
    }
  }
  throw new Error("Unable to determine entity class.");
}

As the above example works for finding the genericized type of a method, two methods on Class that may be helpful for finding generics defined at the Class level are getGenericSuperclass() and getGenericInterfaces().

No comments: