Monday 29 June 2015

Generics on java classes / methods


It is possible to generic your own Java classes. Generics is not restricted to the predefined classes in the Java API's. 

Here is a simple example:
public class GenericFactory<E> {

    Class theClass = null;

    public GenericFactory(Class theClass) {
        this.theClass = theClass;
    }

    public E createInstance()
    throws IllegalAccessException, InstantiationException {
        return (E) this.theClass.newInstance();
    }
}
The <E> is a type token that signals that this class can have a type set when instantiated. 

Here is an example of how:
GenericFactory<MyClass> factory =
    new GenericFactory<MyClass>(MyClass.class);

MyClass myClassInstance = factory.createInstance();
Notice how it is not necessary to cast the object returned from the factory.createInstance() method. 

The compiler can deduct the type of the object from the generic type of the GenericFactory created, because you specified the type inside the <>.

Each instance of the GenericFactory can be generified to different types. Here are two examples:
GenericFactory<MyClass> factory =  new GenericFactory<MyClass>(MyClass.class);
MyClass myClassInstance = factory.createInstance();

GenericFactory<SomeObject> factory = new GenericFactory<SomeObject>(SomeObject.class);

SomeObject someObjectInstance = factory.createInstance();

Generics in Methods

It is possible to generify methods in Java. Here is an example:
public static <T> T addAndReturn(T element, Collection<T> collection){
    collection.add(element);
    return element;
}
This method specifies a type T which is used both as type for the element parameter and the generic type of the Collection. Notice how it is now possible to add elements to the collection. This was not possible if you had used a wildcard in the Collection parameter definition.
So, how does the compiler know the type of T?
The answer is, that the compiler infers this from your use of the method. For instance:
String stringElement = "stringElement";
List<String> stringList = new ArrayList<String>();

String theElement = addAndReturn(stringElement, stringList);    


Integer integerElement = new Integer(123);
List<Integer> integerList = new ArrayList<Integer>();

Integer theElement = addAndReturn(integerElement, integerList);    
Notice how we can call the addAndReturn() method using both String's and Integer's and their corresponding collections. The compiler knows from the type of the T parameter and collection<T>parameter definitions, that the type is to be taken from these parameters at call time (use time).
The compiler can even perform more advanced type inference. For instance, the following call is also legal:
String stringElement = "stringElement";
List<Object> objectList = new ArrayList<Object>();

Object theElement = addAndReturn(stringElement, objectList);    
In this case we are using two different types for T: String and Object. The compiler then uses the most specific type argument that makes the method call type correct. In this case it infers T to be Object.
The inverse is not legal though:
Object objectElement = new Object();
List<String> stringList = new ArrayList<String>();

Object theElement = addAndReturn(objectElement, stringList);
In this case the compiler infers, that for the method call to be type safe, T must be a String. TheobjectElement passed in for the T element parameter must then also be a String (and it isn't). Therefore the compiler will report an error
Class Objects as Type Literals
Class objects can be used as type specifications too, at runtime. For instance, you can create a generified method like this:
public static <T> T getInstance(Class<T> theClass)
    throws IllegalAccessException, InstantiationException {

    return theClass.newInstance();
}
Here are a few examples of calls to the getInstance() method:
String string   = getInstance(String.class);

MyClass myClass = getInstance(MyClass.class);
As you can see the return type changes depending on what class object you pass in as parameter to the method. This can be quite handy in database API's like Butterfly Persistence where you read objects from a database. Here is an example method definition:
public static <T> T read(Class<T> theClass, String sql)
    throws IllegalAccessException, InstantiationException {

    //execute SQL.

    T o = theClass.newInstance();
    //set properties via reflection.

    return o;
}
Here is how you would call the read() method:
Driver employee   = read(Driver.class, "select * from drivers where id=1");

Vehicle vehicle   = read(Vehicle.class, "select * from vehicles where id=1");


No comments:

Post a Comment