Prev | Next |
Java Generics
Generics are a facility of generic programming that were added to the Java programming language in 2004 within the official version J2SE 5.0. They were designed to extend Java's type system to allow a type or method to operate on objects of various types while providing compile-time type safety.
Why are generics necessary?
Lets assume you implement a class/method to sort an array of integers. Is it necessary to have a separate implementation of floats, strings, points, lines, etc? Not really. Java generic methods and generic classes enable programmers to specify:
- with a single method declaration, a set of related methods or,
- with a single class declaration, a set of related types.
We will see an implementation of a generic method followed by a generic class. This topic will be dealt in the most simplistic way. Very rarely you will have to code generic classes/methods. Java Collections Framework defines a number of classes that are implemented generically. In most cases, using them would be sufficient.
1. Generic Methods
You can write a single generic method declaration that can be called with arguments of different types. Based on the types of the arguments passed to the generic method, the compiler handles each method call appropriately. Following are the rules to define generic methods.
- All generic method declarations have a type parameter section delimited by angle brackets (< and >) that precedes the method's return type ( <T> in the next example).
- Each type parameter section contains one or more type parameters separated by commas. A type parameter, also known as a type variable, is an identifier that specifies a generic type name.
- The type parameters can be used to declare the return type and act as placeholders for the types of the arguments passed to the generic method, which are known as actual type arguments.
- A generic method's body is declared like that of any other method. Note that type parameters can represent only reference types, not primitive types (like int, double and char).
Following example illustrates how we can print data of different types using a single generic method:
public class PrintGeneric { public static <T> void printAnyType(T x) { System.out.println("I am a variable of " + x.getClass()); System.out.println("The parent of my " + x.getClass().getGenericSuperclass()); System.out.println(x); System.out.println(); } }
You can now write a driver (PrintGenericTest.java) as follows to check the working.
public class PrintGenericTest { public static void main(String[] args) { PrintGeneric.printAnyType(new Integer(10)); // Print an integer PrintGeneric.printAnyType(new Double(10.5)); // Print a double PrintGeneric.printAnyType(new Character('c')); // Print a character PrintGeneric.printAnyType(new String("generics")); // Print a string } }
You can also add the following statement to the driver to print a variable of type Point, Line, etc. Check it out.
- PrintGeneric.printAnyType(new Point(2,3));
- PrintGeneric.printAnyType(new Line(new Point(), new Point(2,3));
If you had implemented toString() method in the Point class it will print the output of the form (x,y). If not, it will print the address such as Point@55f96302 since it points to a memory location.
Exercises
1. Implement a method printAnyArray() to print the array elements.
public static <T> void printAnyArray(T[] arr) { // Your code here. Loop through the array and print // Write a Test Driver to check its working for all types of arrays // i.e. Integer, Double, Character and String (ArrayGenericTest.java) }
2. Implement a new method that takes two variables of same type as arguments, add them and prints the result. For number types, it is the usual add. For char and string types, add implies concatenation. For Point types, Point's add method must be invoked.
(Hint: You need to check the type before deciding what to do.)
Write a test driver to check its working (AddGenericTest.java)
3. Include a new method that takes an array of any type as input (as above), checks the type first, sorts them and prints the sorted array.
Write a test driver to check its working (SortGenericTest.java)
2. Generic Classes
A generic class declaration looks like a non-generic class declaration, except that the class name is followed by a type parameter section.
The type parameter section of a generic class can have one or more type parameters separated by commas. These classes are known as parameterized classes or parameterized types because they accept one or more parameters.
Following example illustrates how we can define a generic class. The class Box contains one attribute whose type can be anything. Here T refers to a generic type which is known at compile time.
public class Box<T> { private T t; public void set(T t) { this.t = t; } public T get() { return t; } }
The driver class to use the generic class Box, say BoxDriver, is implemented as follows:
public class BoxDriver { public static void main(String[] args) { Box<Integer> integerBox = new Box<Integer>(); // Use Integer not int Box<String> stringBox = new Box<String>(); Box<Point> pointBox = new Box<Point>(); Box<Line> lineBox = new Box<Line>(); integerBox.set(new Integer(10)); stringBox.set(new String("Hello World")); pointBox.set(new Point(2,3)); lineBox.set(new Point(), new Point(2,3)); System.out.printf("Integer value :%d\n", integerBox.get()); System.out.printf("String value :%s\n", stringBox.get()); System.out.printf("Point value :%s\n", pointBox.get()); System.out.printf("Line value :%s\n", lineBox.get()); } }
The last two statements will print the point in (x,y) form and line in y = mx + c form provided toString() was implemented.
Exercise
1. Define a generic class for representing Family. It can be a human family or dog family or family of any living species. A family contains father, mother and array of children. Given below is the driver code.
public class Test { public static void main(String[] args) { Human hf = new Human("Adam", 50); Human hm = new Human("Eve", 45); Human[] hc = new Human[3]; hc[0] = new Human("Cain", 20); hc[1] = new Human("Abel", 15); hc[2] = new Human("Seth", 10); Family<Human> fm = new Family<Human>(hf,hm,hc); fm.getFather().print(); fm.getMother().print(); for (int i=0; i<hc.length; i++) fm.getChild(i).print(); Dog df = new Dog("Jimmy", 8); Dog dm = new Dog("Julie", 7); Dog[] dc = new Dog[2]; dc[0] = new Dog("toto", 1); dc[1] = new Dog("nimo", 2); Family<Dog> fd = new Family<Dog>(df,dm,dc); fd.getFather().print(); fd.getMother().print(); for (int i=0; i<dc.length; i++) fd.getChild(i).print(); } }
You need to implement the generic (or container) class Family plus the contained classes Human and Dog classes. The Human and Dog classes need to implement the constructor and print methods which are being invoked by the above driver.
2. Define a generic class Dozen for holding a dozen items. You need to define methods for inserting an item, removing an item, replacing an item. The item can be points, lines, squares, triangles or practically any entity. You have already implemented classes for Point, Line, Square, Triangle as part of the lab session. So, you can use them directly here. Implement driver to instantiate a dozen of each and invoke methods on them.