Exploiting Generics Metadata

Engineering | Rob Harrop | September 29, 2006 | ...

It is a common misconception that I hear when talking with clients that all information about generic types is erased from your Java class files. This is entirely untrue. All static generic information is maintained, and only generic information about individual instances is erased. So if I have a class Foo that implements List<String>, then I can determine that Foo implements the List interface parameterised by String at runtime. However, if I instantiate an instance of ArrayList<String> at runtime, I cannot take that instance and determine its concrete type parameter (I can determine that ArrayList requires type parameters). In this entry I’m going to show you a practical usage for some of the available generics metadata that simplifies the creation of strategy interfaces and implementations that differ by the type of object they process.

A pattern that I see occurring in many applications is the use of some kind of strategy interface with concrete implementations each of which handles a particular input type. For example, consider a simple scenario from the investment banking world. Any publicly traded company can issue Corporate Actions that bring about an actual change to their stock. A key example of this is a dividend payment which pays out a certain amount of cash, stock or property per shared to all shareholders. Within an investment bank, receiving notification of these events and calculating the resultant entitlements is very important in order to keep trading books up to date with the correct stock and cash values.

As a concrete example of this, consider BigBank which holds 1,200,000 IBM stock. IBM decides to issue a dividend paying $0.02 per share. As a result, BigBank needs to receive notification of the dividend action and, at the appropriate point in time, update their trading books to reflect the additional $24,000 of cash available.

The calculation of entitlement will differ greatly depending on which type of Corporate Action is being performed. For example, a merger will most likely result in the loss of stock in one company and the gain of stock in another.

If we think about how this might look in a Java application we could assume to see something like this (heavily simplified) example:


public class CorporateActionEventProcessor {

    public void onCorporateActionEvent(CorporateActionEvent event) {
        // do we have any stock for this security?

        // if so calculate our entitlements
    }
}

Notifications about events probably come in via a number of mechanisms from external parties and then get sent to this CorporateActionEventProcessor class. The CorporateActionEvent interface might be realised via a number of concrete classes:


public class DividendCorporateActionEvent implements CorporateActionEvent {

    private PayoutType payoutType;
    private BigDecimal ratioPerShare;

    // ...
}

public class MergerCorporateActionEvent implements CorporateActionEvent {

    private String currentIsin; // security we currently hold
    private String newIsin; // security we get
    private BigDecimal conversionRatio;
}

The process of calculating entitlements might be encapsulated by an interface such as this:


public interface EntitlementCalculator {
    void calculateEntitlement(CorporateActionEvent event);
}

Along with this interface we are likely to see a number of implementations looking like this:


public class DividendEntitlementCalculator implements EntitlementCalculator {

    public void calculateEntitlement(CorporateActionEvent event) {
        if(event instanceof DividendCorporateActionEvent) {
            DividendCorporateActionEvent dividendEvent = (DividendCorporateActionEvent)event;
            // do some processing now
        }
    }
}

Our CorporateActionEventProcessor might then look something like this:


public class CorporateActionEventProcessor {

    private Map<Class, EntitlementCalculator> entitlementCalculators = new HashMap<Class, EntitlementCalculator>();

    public CorporateActionEventProcessor() {
        this.entitlementCalculators.put(DividendCorporateActionEvent.class, new DividendEntitlementCalculator());
    }

    public void onCorporateActionEvent(CorporateActionEvent event) {
        // do we have any stock for this security?

        // if so calculate our entitlements
        EntitlementCalculator entitlementCalculator = this.entitlementCalculators.get(event.getClass());
    }
}

Here you can see we maintain a Map of CorporateActionEvent type to EntitlementCalculator implementation and we use this to locate the correct EntitlementCalculator for each CorporateActionEvent.

Looking back at this example the first glaring issue is that EntitlementCalculator.calculateEntitlement is typed to receive only CorporateActionEvent resulting in a type check and cast inside each implementation. We can easily fix this using generics:


public interface EntitlementCalculator<E extends CorporateActionEvent> {
    void calculateEntitlement(E event);
}

public class DividendEntitlementCalculator implements EntitlementCalculator<DividendCorporateActionEvent> {

    public void calculateEntitlement(DividendCorporateActionEvent event) {

    }
}

As you can see we introduced a type parameter, E, which is bound to extend CorporateActionEvent. We then define that DividendEntitlementCalculator implements EntitlementCalculator<DividendCorporateActionEvent> resulting in E being replaced with DividendCorporateActionEvent as appropriate within the DividendEntitlementCalculator. removing the need to type check and cast.

The CorporateActionEventProcessor class continues to work as it is, however there is now some duplication and also the chance for errors. When registering a particular EntitlementCalculator we still have to specify the type it handles even though this is already specified in the class definition. Given this, it is possible to register an EntitlementCalculator for a type that it cannot possibly handle:


public CorporateActionEventProcessor() {
        this.entitlementCalculators.put(MergerCorporateActionEvent.class, new DividendEntitlementCalculator());
}

Thankfully it is pretty easy to fix this by pulling the parameter type from the generic interface declaration and using this as the key type:


public void registerEntitlementCalculator(EntitlementCalculator calculator) {
    this.entitlementCalculators.put(extractTypeParameter(calculator.getClass()), calculator);
}

We start by adding a registerEntitlementCalculator method which delegates to extractTypeParameter to find the type parameter for the EntitlementCalculator class.


private Class extractTypeParameter(Class<? extends EntitlementCalculator> calculatorType) {
    Type[] genericInterfaces = calculatorType.getGenericInterfaces();

    // find the generic interface declaration for EntitlementCalculator<E>
    ParameterizedType genericInterface = null;
    for (Type t : genericInterfaces) {
        if (t instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType)t;
            if (EntitlementCalculator.class.equals(pt.getRawType())) {
                genericInterface = pt;
                break;
            }
        }
    }

    if(genericInterface == null) {
        throw new IllegalArgumentException("Type '" + calculatorType
               + "' does not implement EntitlementCalculator<E>.");
    }

    return (Class)genericInterface.getActualTypeArguments()[0];
}

Here we start by grabbing the Type[] representing the generic interfaces of the EntitlementCalculator type by calling Class.getGenericInterfaces(). This method differs greatly from Class.getInterfaces() which returns Class[]. Calling DividendEntitlementCalculator.class.getInterfaces() returns a single Class instance representing the EntitlementCalculator type. Calling DividendEntitlementCalculator.class.getGenericInterfaces() returns a single ParameterizedType instance representing the EntitlementCalculator type with a type argument of DividendCorporateActionEvent. Calling getGenericInterfaces() on a class with both generic and non-generic interfaces will return an array with both Class and ParameterizedType instances.

Next, we iterate over the Type[] and find the ParameterizedType instance whose “raw type” is EntitlementCalculator. From this we can extract the type argument for E using getTypeArguments() and returning the first array instance - which we know will always exist in this scenario.

Calling code can simply pass in the EntitlementCalculator implementations as required:


CorporateActionEventProcessor processor = createCorporateActionEventProcessor();
processor.registerEntitlementCalculator(new DividendEntitlementCalculator());

This is now a really nice API and be extended even further with something like Spring where you can use ListableBeanFactory.getBeansOfType() to locate all configured EntitlementCalculator implementations and automatically register them with the CorporateActionEventProcessor.

What's Next?

One interesting situation some of you may have noticed here is that it is entirely possible to have code like this:


EntitlementCalculator calculator = new DividendEntitlementCalculator();
calculator.calculateEntitlement(new MergerCorporateActionEvent());

This code will compile just fine but we know that the DividendEntitlementCalculator.calculateEntitlement method only accepts a DividendCorporateActionEvent object. So why does that compile? And, since it does compile what happens at runtime? Well, to answer the second question first - Java still ensure type safety by throwing ClassCastException at runtime. Why this works and to answer the question of why this example actually compiles I'll be writing another entry soon...

Further Reading

Securities Operations

Corporate Actions

Generics in the Java Programming Language

Get the Spring newsletter

Thank you for your interest. Someone will get back to you shortly.

Get ahead

VMware offers training and certification to turbo-charge your progress.

Learn more

Get support

Tanzu Spring Runtime offers support and binaries for OpenJDK™, Spring, and Apache Tomcat® in one simple subscription.

Learn more

Upcoming events

Check out all the upcoming events in the Spring community.

View all