Wednesday, October 31, 2007

Generics in Java Programming.

Generics

Generics were added to the Java language syntax in version 1.5. This means that code using Generics will not compile with Java 1.4 and less.

Java was long criticized for the need to explicitly type-cast an element when it was taken out of a "container/collection" class. There was no way to enforce that a "collection" class contains only one type of object. This is now possible since Java 1.5.

In the first couple of years of Java evolution, Java did not have a real competitor. This has changed by the appearance of Microsoft C#. With Generics Java is better suited to compete against C#.

What are Generics?

Generics are so called because this language feature allows methods to be written generically, with no foreknowledge of the type on which they will eventually be called upon to carry out their behaviors. A better name might have been type parameter argument. Because, it is basically that, to pass a Type as a parameter to a class at creation time.

When an object is created, parameters can be passed to the created object, through the constructor. Now with Generics, we can also pass in Types. The type-place-holders will be replaced with the specified type, before the object is created.

Type parameter arguments can be set:
for a class
When an object is created from that class the type-parameter-argument will be replaced with the actual Type.
public class Person
{
private Person person;
...
}...// --- Create an Employee person ---
Person emplPerson = new Person();
...
// --- Create a Customer person ---
Person custPerson = new Person();

for a method

Just like class declarations, method declarations can be generic--that is, parameterized by one or more type parameters.
static public void assign( Person person, T obj )
{
person.setPerson( obj );
}
use of generics is optional


For backwards compatibility with pre-Generics code, it is okay to use generic classes without the generics type specification thing (). In such a case, when you retrieve an object reference from a generic object, you will have to manually typecast it from type Object to the correct type. The compiler should also warn about unsafe operations.

Introduction

Java is a "strongly" typed language. That's why it is so easy to use. Many potential problems are caught by the compiler. One area where Java was criticized was regarding the "Container/Collection" objects. Container objects are objects that contain other objects. Before Generics were introduced there was no way to ensure that a container object contains only one type of objects. When an object was added to a container, it was automatically cast to Java Object. When it was taken out an explicit cast was needed. Normally an explicit cast is checked by the compiler.

String st = "This is a String";
...
Integer integer = (Integer) st; // --- Compilation Error --

But in the case of container classes, the compiler was not able to catch an invalid type casting.

1 Collection collString = new ArrayList();
2 collString.add( "This is a String" );
...
3 Integer integer = (Integer) collString.get(0); // --- No Compilation Error; RunTime CastException

Just looking at line 3, we do not know what type of objects collString contains. If that contains Integers then the code is fine.

The above code using Generic:
Collection<String> collString = new ArrayList<String>();
collString.add( "This is a String" );
...
Integer integer = (Integer) collString.get(0); // --- Compilation Error

collString is a container object, that can contain only String objects, nothing else, so when we get out an element it can be casted only to class that normally a String can be casted.

With Generics, Java strict type checking can be extended to container objects. Using Generics with container classes, gives an impression that a new container type is created, with each different type parameter. Before Generics:

Collection collCustomer = new ArrayList();
collCustomer.add( new Customer() );
...
Collection collObject = collCustomer; // --- No problem, both collObject and collCustomer have the same type

With generics:
Collection collCustomer = new ArrayList();
collCustomer.add( new Customer() );
...
Collection(object) collObject = collCustomer; // --- Compilation Error

Both collObject and collCustomer have the same type, BUT it is against the Generic rule, that is collCustomer can contain only Customer objects, and collObject can contain only Object object. So there is an additional check to the normal type checking, the type of the parameter type has to be matched too.

Note for C++ programmers

Java Generics are similar to C++ Templates in that both were added for the same reason. The syntax of Java Generic and C++ Template are also similar.

There are some differences however. The C++ template can be seen as a kind of macro, that generates code before compilation. The generated code depends on how the Template class is referenced. The amount of code generated depends on how many different types of classes are created from the Template. C++ Templates do not have any run-time mechanisms. The compiler creates normal code to substitute the template, similar to any 'hand-written' code.

In contrast, Java Generics are built into the language. The same Class object handles all the Generic type variations. No additional code is generated, no matter how many Generic objects are created with different type parameters. For example.

Collection collString = new ArrayList<String>();
Collection collInteger = new ArrayList();

There is only one Class object created. In fact, at runtime, both these objects appear as the same type (both ArrayList's). The generics type information is erased during compilation (type erasure). This means, for example, that if you had function that takes Collection as an argument, and that collection happened to be empty, your function would have no way of instantiating another T object, because it doesn't know what T was.

The Class
class itself is generic since Java 1.5.

public final class Class extends Object implements Serializable, GenericDeclaration, Type, AnnotatedElement{
...
}

The T type here represents the type that is handed to the Class object. The T type will be substituted with the class being loaded.

Class

Since Java 1.5, the class java.lang.Class is generic. It is an interesting example of using genericness for something other than a container class.

For example, the type of String.class is Class, and the type of Serializable.class is Class. This can be used to improve the type safety of your reflection code.

In particular, since the newInstance() method in Class now returns a T, you can get more precise types when creating objects reflectively.

Now we can use the newInstance() method to return a new object with exact type, without casting.

Customer cust = Utility.createAnyObject(Customer.class); // - No casting
...
public static T createAnyObject(Class cls) {
T ret = null;
try
{
ret = cls.newInstance();
}
catch (Exception e) {
// --- Exception Handling
}
return ret;
}

And the above code without Generics:

Customer cust = (Customer) Utility.createAnyObject(Customer.class); // - Casting is needed
...
public static Object createAnyObject(Class cls)
{
Object ret = null;
try
{
ret = cls.newInstance();
}
catch (Exception e)
{ // --- Exception Handling }
return ret;
}

Get exact type when getting JavaBean property, using reflection

See the following code where the method will return the exact type of the Java Bean property, based on how it will be called.
// --- Using reflection, get a Java Bean property by its name ---

public static T getProperty(Object bean, String propertyName)
{
if (bean == null propertyName == null propertyName.length() == 0)
{
return null;
} // --- Based on the property name build the getter method name ---
String methodName = "get" +propertyName.substring(0,1).toUpperCase() + propertyName.substring(1);
T property = null;
try
{
java.lang.Class c = bean.getClass();
java.lang.reflect.Method m = c.getMethod(methodName, null);
property = (T) m.invoke(bean, null);
}
catch (Exception e)
{ // --- Handle exception -- }
return property;
}

Variable Argument

With Generic it is very easy to define a method with variable argument. Before generic usually passing in array was close to the variable argument. The only requirement is that the arguments in the list must have the same type.

The following code illustrates the method that can be called with variable arguments:
/**
* Method using variable argument list
* @param
* @param args
*/
public static List makeAList(T... args)
{
List argList = new ArrayList();
for (int i = 0; i <>
{
argList.add(args[i]);
}
return argList;
}

And the above method can be called with variable argument, see below:

List<String> list1 = makeAList("One", "Two", "Three");
List<String> list2 = makeAList("One", "Two", "Three", "Four");

In the above calls the arguments must be String. If we for the T, then we can pass in any kind of objects regardles of their type. See below:

List list3 = makeAList("One", 10);

Note: the number 10 in the above code will be converted (autoboxed) to Integer.

See also: java.util.Arrays.asList(T... a)

Wildcard Types

As we have seen above, generics give the impression that a new container type is created with each different type parameter. We have also seen that in addition to the normal type checking, the type parameter has to match as well when we assign generics variables.

In some cases this is too restrictive. What if we would like to relax this additional checking? What if we would like to define a collection variable that can hold any generic collection, regardless of the parameter type it holds?

Wildcard

The wildcard type is represented by the character , and pronounced Unknown, or Any-Type. This Unknown type matches anything, if it is used only by itself. Any-Type can be express also by . Any-Type includes Interfaces, not only Classes.

No comments: