Generics

From Wikibooks, open books for an open world
Jump to: navigation, search

Nested Classes Java Programming
Generics
Collections
Navigate Classes and Objects topic: v  d  e )


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 (e.g., to forbid at compile time that an Integer object is added to a Collection that should only contain Strings). 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#. Similar constructs to Java Generics exist in other languages, see Generic programming for more information.

What are Generics? [edit]

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.
Computer code
public class Person<T>
{
   private Person<T> person;
}


Computer code
// Create an Employee person
Person<Employee> emplPerson = new Person<Employee>();


Computer code
// Create a Customer person
Person<Customer> custPerson = new Person<Customer>();


for a method 
Just like class declarations, method declarations can be generic--that is, parameterized by one or more type parameters.
Computer code
public static <T> Object assign(Person<T> person, T obj)
{
   person.setPerson(obj);
   return 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 (<T>). 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 [edit]

Java is a strongly typed language. That's one of the reasons 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 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.


Computer code
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.

Computer code
1 List 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 below code using Generic:

Computer code
List<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: With generics:
Computer code
Collection collCustomer = new ArrayList();
collCustomer.add(new Customer());
...
Collection collObject = collCustomer;
// No problem,
// both collObject and collCustomer
// have the same type
Computer code
Collection<Customer> collCustomer = new ArrayList<Customer>();
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 [edit]

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.

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

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<T> 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.

Computer code
public final class '''Class<T>''' 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<T> [edit]

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<String>, and the type of Serializable.class is Class<Serializable>. 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. 
Computer code
Customer cust = Utility.createAnyObject(Customer.class);  // No casting
...
public static <T> T createAnyObject(Class<T> cls)
{
    T ret = null;
    try
    {
        ret = cls.newInstance();
    }
    catch (Exception e)
    {
        // Exception Handling
    }
    return ret;
}


And the above code without Generics:

Computer code
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.
Computer code
// Using reflection, get a Java Bean property by its name
public static <T> 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 [edit]

Using Generics, it is very easy to define a method with a dynamic number of arguments. Before generics, this was not possible in Java—if a likewise feature was needed, this was mostly done by passing an array. The only requirement for using a dynamic number of arguments using Generics is that the arguments in the list must have the same type.

The following code illustrates a method that can be called with a dynamic number arguments:

Computer code
/**
 * Method using variable-length argument list
 * @param <T>
 * @param args
 */

public static <T> List<T> makeAList(T... args)
{
    List<T> argList = new ArrayList<T>();
    for (int i = 0; i < args.length; i++)
    {
        argList.add(args[i]);
    }
    // List<T> argList = Arrays.asList(args);
    return argList;
}

The above method can be called with a dynamic number of arguments, for example:

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

In the above example calls, the arguments must be of type String. If we write <? extends Object> instead of T, then we can pass any kind of objects, regardles of their type:

Computer code
List<? extends Object> list3 = makeAList("One", 10, new StringBuffer(), new LinkedList());

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

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

Wildcard Types [edit]

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 <? extends Object>. Any-Type includes Interfaces, not only Classes.

So now we can define a collection whose element type matches anything. See below:

Computer code
Collection<?> collUnknown;


Note that we can not add anything to this collection. We can only take out elements of type Object from it. So what is the use of this variable if we can not add anything to the collection it represents? The use of this new construct will be clear when you want to create a generic method that takes any collection.

Computer code
public static void printElements( Collection<?> anycoll )
{
   Iterator<?> iter = anycoll.iterator();
   while ( iter.hasNext() )
   {
      System.out.print( iter.next() );
   }  
}


Wildcard for a specific type of classes 

<? extends ClassName> specifies a restriction on the types of classes that may used.

For example, to create a collection that may only contain "Serializable" objects, specify:

Computer code
Collection'''<? extends Serializable>''' serColl = new ArrayList<<code>[[Java Programming/API/java.lang.String|String]]</code>>();


The above code is valid because, the String class is serializable. Use of a class that is not serializable would cause a compilation error.

The following collection can only contain objects that extend the class Animal.

Computer code
class Dog extends Animal
{
 ...
}


Computer code
// Create "Animal Collection" variable
Collection<? extends Animal> animalColl = new ArrayList<Dog>();


<? super ClassName> specifies a restriction on the types of classes that may be used.

For example, to declare a Comparator that can compare Dogs, you use

Computer code
Comparator<? super Dog> myComparator;


Now suppose you define a comparator that can compare Animals:

Computer code
class AnimalComparator implements Comparator<Animal>
{
  int compare(Animal a, Animal b) { //...
  }
}


Since Dogs are Animals, you can use this comparator to compare Dogs also. Comparators for any superclass of Dog can also compare Dog; but comparators for any strict subclass cannot.

Computer code
Comparator<Animal> myAnimalComparator = new AnimalComparator();

static int compareTwoDogs(Comparator<? super Dog> comp, Dog dog1, Dog dog2) {
  return comp.compare(dog1, dog2);
}


The above code is valid because, the Animal class is a supertype of the Dog class. Use of a class that is not a supertype would cause a compilation error.

Nested Classes Java Programming
Generics
Collections