Berichten met label generics

Generics, code duplication and type safety

Generics is a wonderful mechanism to increase type-safety in code, while at the same time preventing code duplication. However, generics is not always something we want to have exposed to an end user of a library. The specific way in which generics is implemented in Java cause some quirks; below, we will introduce these, and provide some mechanisms to work around them in a clean way.

Introduction

Recently, we needed a mechanism to store value objects in stores (which can create and delete the value objects), together with the possibility to associate these using another value object.

So, we have objects, which are grouped in stores, and objects are linked by associations, which reside in their own stores. Some commonalities immediately pop up, so we can factor those out, and create a nice generic mechanism for them. Furthermore, we do not want to expose the end user to generics, and give him a LicenseStore which holds LicenseObjects, a UserStore which holds UserObjects, and a License2UserAssociation which links two of the objects together, living in its own License2UserStore.

This document contains a number of references to code; this can be found here.

Value objects

We will not go into the value objects in detail; they can be found in the code snippets accompanying this document.

Associations

The Association can link two objects together, and do so while respecting their specific types.

public class Association {
    private final FROM m_from;
    private final TO m_to;

    public Association(FROM from, TO to) {
        m_from = from;
        m_to = to;
    }

    public String toString() {
        return "This is an association from " + m_from.getName() + " to " + m_to.getName() + ".";
    }

    // ...
}

AssociationStore has facilities for managing these associations, so the following shape comes to mind.

public class AssociationStore  {
    // ...
    public Association create(FROM from, TO to) {
        return new Association(from, to);
    }
}

End user view

So far, so good. If we want to use an association store, we could do something like this.

    LicenseObject lo = new LicenseObject("myLicense");
    UserObject uo = new UserObject("myUser");

    AssociationStore store = new AssociationStore();
    Association association = store.create(lo, uo);

    System.out.println(association.toString());

When run, this gives us the following output.

This is an association from myLicense to myUser.

All code up to here is available as stores_1, so you can play around with it.

Moving away from generics

As stated in the introduction, we do not want to bother the user with generics. In stead, we will introduce specialized descendant classes for the associations.

public class License2UserAssociation extends Association {

    public License2UserAssociation(LicenseObject from, UserObject to) {
        super(from, to);
    }

}

and

public class License2UserStore extends AssociationStore{

}

Great, let’s try to use this.

    LicenseObject lo = new LicenseObject("myLicense");
    UserObject uo = new UserObject("myUser");

    License2UserStore store = new License2UserStore();
    License2UserAssociation association = store.create(lo, uo);

    System.out.println(association.toString());

Hey, that gives us a compiler error (this setup is available as stores_2) on the create(…). What’s that?

Exception in thread "main" java.lang.Error: Unresolved compilation problem:
    Type mismatch: cannot convert from Association to License2UserAssociation
    at stores_2.Main.main(Main.java:10)

As it turns out, this has not that much to do with generics. create(…) will return a Association<LicenseObject, UserObject>, and we expect a License2UserAssociation, which is a subtype of this; hence, we have introduced an invalid downcast.

Type erasure and error locations

OK, so we want create(…) to return something of the proper type. That means that the AssociationStore should know about the type of association they handle.

public class AssociationStore > {
    // ...
    public ENTRY create(FROM from, TO to) {
        return (ENTRY) new Association(from, to);
    }
}

and

public class License2UserStore extends AssociationStore{ ... }

We have now moved the ClassCastException from our main(…) to the create(…) of AssociationStore. Just for fun, let’s try to introduce a cast there, and see what happens.

    return (ENTRY) new Association(from, to);

Note that the code in our main(…) now actually compiles. However, running this code will give us the following:

Exception in thread "main" java.lang.ClassCastException: stores_3.Association
    at stores_3.Main.main(Main.java:10)

All right, a ClasCastException, we know that, we just moved the source of the exception to other code. But hey, why does it go wrong in our main? As you can see in stores_3, there are no warnings on that line. Here we see an artifact of the way generics is implemented in Java.

What does the VM see?

Generics in Java are implemented as a compile-time checking mechanism, and all those fancy FROM and ENTRY things are erased at runtime, and casts are introduced. So, let’s see what the compiler would actually deliver to the runtime. Doing what the compiler would do, we end up with

// (Generics removed)
public class AssociationStore {
    // ...
    public Association create(ValueObject from, ValueObject to) {
        return new Association(from, to);
    }
}

Note that, indeed, all our generics are gone, and replaced by the ‘highest possible’ type instead, that is, whatever followed extends. Our cast is gone too.

Our main(…) never used any generics, so that will be identical. Now, create(…) will return an Association, while main(…) expects it to return a License2UserAssocation; that’s we’re the ClassCastException comes from, and therefore it only shows up in our using code.

Sidenote

As a sidenote, this erasure has some more implications. Suppose that we want to extend our Association with some functionality to get from one side of the association to the other, something like

    public FROM getOther(TO to) {
        return m_from;
    }

    public TO getOther(FROM from) {
        return m_to;
    }

The compiler has the following to say about this:

Exception in thread "main" java.lang.Error: Unresolved compilation problems:
    Method getOther(TO) has the same erasure getOther(ValueObject) as another method in type Association
    Method getOther(FROM) has the same erasure getOther(ValueObject) as another method in type Association

    at stores_3.Association.(Association.java:16)
    at stores_3.AssociationStore.create(AssociationStore.java:6)
    at stores_3.Main.main(Main.java:10)

So, so far for type erasure.

Solutions

Up to now, we have built up a mechanism of generic base classes and specialized, non-generic descendants. However, it does not really ‘work’ at the moment… Let’s consider some options to ‘make it work’.

The main thing we run into is that we cannot type-safely create new inhabitants of our association store. The ENTRY gets erased at runtime, so how about passing ENTRY’s real class, and using reflection to create the new objects?

So, we end up with


public class AssociationStore > {

    private final Class m_entryClass;

    public AssociationStore(Class entryClass)
    {
        m_entryClass = entryClass;
    }

    public ENTRY create(FROM from, TO to) {
        try {
            return m_entryClass.getConstructor(ValueObject.class, ValueObject.class).newInstance(from, to);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

(Note that we get the constructor by ValueObject.class, since TO and FROM are not available at runtime.)

In License2UserAssociation, we need to ‘pass up’ the class,

public class License2UserStore extends AssociationStore{

    public License2UserStore() {
        super(License2UserAssociation.class);
    }

}

That does not look all too bad. Until we run it; our printStackTrace tells us

java.lang.NoSuchMethodException: stores_4.License2UserAssociation.(stores_4.ValueObject, stores_4.ValueObject)
    at java.lang.Class.getConstructor0(Class.java:2678)
    at java.lang.Class.getConstructor(Class.java:1629)
    at stores_4.AssociationStore.create(AssociationStore.java:15)
    at stores_4.Main.main(Main.java:10)

Damn. Why’s that? Let’s take a look at the Javadoc of getConstructor. It says “The parameterTypes parameter is an array of Class objects that identify the constructor’s formal parameter types, in declared order.”. True, the constructor we’re looking for is not declared using ValueObject, but using LicenseObject and UserObject.

Fine, let’s pass up those too (the following code is available as stores_4).

public class AssociationStore > {

    private final Class m_entryClass;
    private final Class m_fromClass;
    private final Class m_toClass;

    public AssociationStore(Class fromClass, Class toClass, Class entryClass)
    {
        m_fromClass = fromClass;
        m_toClass = toClass;
        m_entryClass = entryClass;
    }

    public ENTRY create(FROM from, TO to) {
        try {
            return m_entryClass.getConstructor(m_fromClass, m_toClass).newInstance(from, to);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

and

public class License2UserStore extends AssociationStore{

    public License2UserStore() {
        super(LicenseObject.class, UserObject.class, License2UserAssociation.class);
    }

}

Victory! This works! However, it has some drawbacks.

First, it is ugly. We have three generic parameters, and we pass up three classes, to use reflection to instantiate a single object.
Second, it’s very brittle. If we ever get a PowerUserObject as a subclass of UserObject, this code will no longer work.

A simpler solution

In stead of the solution above, we can turn to another given tool of object orientation: abstract functions. We can delegate the creation of new inhabitants to the descendant class; something like this.

public abstract class AssociationStore > {

    public ENTRY create(FROM from, TO to) {
        return createNewInhabitant(from, to);
        // and of course store this object...
    }

    protected abstract ENTRY createNewInhabitant(FROM from, TO to);
}

and

public class License2UserStore extends AssociationStore{

    @Override
    protected License2UserAssociation createNewInhabitant(LicenseObject from, UserObject to) {
        return new License2UserAssociation(from, to);
    }

}

There, doesn’t that look way more appealing? We have gotten rid of the passing up of classes, and we get added type safety for free! This structure is an instance of the template pattern. This code is available as stores_5.

Conclusion

What was this all about? I did not have in mind to provide you with a nice and type safe mechanism for creating an AssociationStore. What I did have in mind was to show the good that generics can do (we end up with a completely type safe API, without exposing the end user to generics, and have a minimum of code duplication) while at the same time showing some of the things to ‘think about’ when using generics for more intricate stuff than
Map<String, Object> mymap = new HashMap<<tring, Object>();, such as type erasure and what the runtime will actually 'see'.

,

Nog geen reacties