Documente Academic
Documente Profesional
Documente Cultură
Table of Contents
Point 9: Working with Java Generics..............................................................................................1
Introducing Generics........................................................................................................................2
Overview of Generic Classes..................................................................................................2
Implementing Generic Classes...............................................................................................3
Using Generics with Interfaces.......................................................................................................8
Implementing Generic Interfaces............................................................................................8
Subtyping and Generics........................................................................................................11
Using Wildcards..............................................................................................................................13
Working with Wildcards.........................................................................................................13
Using Generic Methods........................................................................................................17
Related Topics................................................................................................................................19
Introducing Generics
Generics allow you to design Application Programming Interfaces (APIs) that provide general
functionality applicable to multiple types. The type of an object is the information about the class
and hierarchy of classes and interfaces to which the object belongs. Using generics you can specify
the type information of data using a parameter, which means that the type can be a variable or it
can change at runtime.
Generics is a popular model of programming supported by functional languages, such as Haskell.
Various objectoriented languages, such as C++ and Eiffel, also support generics. The C++
language implements generics through templates.
Note A functional language is a language that supports and encourages programming in a
functional style. Functional programming is a style of programming that evaluates
expressions, rather than executing commands with variables. For example, to calculate the
sum of integers from 1 to 10, a functional programming language uses an expression, such
as sum [1..10], rather than executing commands such as:
total = 0;
for (i=1; i<=10; ++i)
total += i;
When you want to retrieve the object stored within the strList object, you need to track the type of
the objects stored in it. You can retrieve the objects stored in the strList object using the following
statement:
String str = (String) strList.iterator().next();
You should typecast the objects returned, into the desired type because Collection objects in Java
return elements of type, Object. Typecasting is an error prone operation because programmers tend
to miss out typecasting code and may issue wrong casts at runtime. This may give rise to bugs at
later stages of development. In addition, typecasting is an unsafe operation because casts may fail
at runtime.
You need not typecast elements when using generics because generics allow you to specify the
type of elements stored in a data structure using a parameter, at the time of program development.
When you use generics in your programs, the compiler inserts typecasts at appropriate places to
implement typecasting.
Using generics, you can state in the code that you require an ArrayList object that stores only String
objects. You specify the type that you require within angled brackets, <>. You can use this notation
when you use a generically defined class from an API in your programs or when you want to create
generic classes yourself. The following code snippet shows how you can instantiate a generic List
object:
ArrayList<String> strList = new ArrayList<String>();
strList.add("Denver");
The first line in the above snippet creates an ArrayList object that stores only strings. When you
retrieve the String object from the ArrayList object, you do not have to typecast it. The following
snippet shows how to retrieve a String object from the ArrayList object, without writing the code for
typecasting:
String str = strList.iterator().next();
You specify the parameter type when instantiating an object of the class. For example, if you create
a class, Graph, which can hold multiple types of data within it, then the declaration for this class
would be:
public class Graph<A> extends SomeClass implements SomeInterface{
}
The above snippet specifies the parameter type of the class within angled brackets as A. You use
this convention when you create custom generic classes. Using a single capital letter is not
mandatory, but doing helps avoid confusion with other data types and class names. JDK 1.5
replaces the entire collection framework in the java.util package with a generic collection framework.
As a result, you can use the generic versions of collection classes in Java programs.
The addition of generic extension to the Java language does not call for a change in Java Virtual
Machine (JVM). This is because the Java compiler applies generics only at compile time. It uses the
parametric information in the source code to provide type safety.
Note
Type safety means an object retains its original type, and errors due to type mismatch
are trapped during compile and runtime. A language and tools associated with the
language should ensure type safety if the language is type safe.
The compiler compiles the source using a technique called erasure. Erasure ensures that the
compiler does not retain any generics code of the source code in the compiled code. The compiler
inserts appropriate casting corresponding to the instances of generics code into the bytecode to
reflect the parametric information. As a result, the compiled bytecode of classes written using
generics do not contain any information relating to generics. This means code written using JDK 1.5
is backward compatible with JRE 1.4.
JDK versions prior to 1.5 implement a subtyping rule by which all objects are of type Object. This
allows you to store objects of multiple types in one data structure or collection class. When creating
arrays you always mention the type of the object to be stored in the array. Generics allow you treat
other data structures and collection classes similar to arrays, whereby you can explicitly state that a
data structure can hold objects of only one type.
Note The collection framework does not implement circular linked lists.
Circular linked lists are linked lists whose last node refers back to the first node of the list. This type
of list is useful when you want to keep traversing the list and accessing the elements in the list
repeatedly in a circular fashion. For example, you can use a circular linked list when you want to
create an animation using few still images as frames.
Note
To learn more about linked lists and other data structures, see the Implementing
Linked Lists in C++ ReferencePoint.
To create a circular linked list using generics, create two classes, Node and CircularList. Listing
191 shows the complete code for the CircularList.java file.
Listing 191: The CircularList.java File
class Node<A>
{
//Declaring the element of type A
A element;
/*
Reference or pointer to the next node in the chain, the reference is parameterized
*/
Node<A> nextNode;
// No argument Constructor
public Node()
{
element = null;
nextNode = null;
}
/*
Constructor that takes two arguments, the element and reference to the next node
*/
public Node(A elem, Node<A> n)
{
element = elem;
nextNode = n;
}
//Setter method for the element member variable
public void setElement(A elem)
{
element = elem;
}
//Getter method for the element member variable
public A getElement()
{
return element;
}
//Setter method for the Node member variable
public void setNextNode(Node<A> n)
{
nextNode = n;
}
//Getter method for the Node member variable
public Node<A> getNextNode()
{
return nextNode;
}
}
//The generic CircularList class
public class CircularList<A>
{
//reference to the head node
protected Node<A> header;
//reference to the tail node
protected Node<A> trailer;
//reference to the current node
protected Node<A> cursor;
//reference to the traveling node
protected Node<A> traverser;
protected int size;
public CircularList()
{
header = new Node<A>();
trailer = new Node<A>();
trailer.setNextNode(header);
cursor = null;
size = 0;
Reprinted for v697039, Verizon
}
public int getSize()
{
return size;
}
public boolean isEmpty()
{
return (size == 0);
}
//adds an element to the end of the list
public void append(A elem)
{
Node<A> temp = new Node<A>();
temp.setElement(elem);
temp.setNextNode(trailer);
if(isEmpty())
{
header.setNextNode(temp);
}else
{
cursor.setNextNode(temp);
}
cursor = temp;
size++;
}
//returns the first node in the list. It does not return header
public Node<A> getFirstNode()
{
return header;
}
/*
Sets the traveling node to the header. Used when traversing list
*/
public void setTraverserToTop()
{
traverser = header;
}
//Returns the last node of the list.
public Node<A> getLastNode()
{
return trailer;
}
/*
Returns the next node in the list from the current position of the traveling node
*/
public Node<A> getNext()
{
traverser = traverser.getNextNode();
if(traverser == header)
{
traverser = traverser.getNextNode();
}else if(traverser == trailer)
{
traverser = traverser.getNextNode().getNextNode();
}
return traverser;
}
}
In the above code, the Node class represents a single node in the list. A Node object contains a
reference to next node in the linked list and a reference to the element that it contains. You can
specify the parameter type of the element, which the node can contain, when creating an instance
of the Node class.
The CircularList class represents a circularly linked list. The CircularList class contains references to
four Node objects, header, trailer, cursor, and traverser. The header object represents the beginning
of the list and the trailer object represents the end of the list. The cursor object represents the
position where the class would add the next element and the traverser object helps in traversing the
list. You can specify the parameter type of the CircularList class when instantiating the class.
Reprinted for v697039, Verizon
A program called Animator uses this CircularList class. The Animator program displays an
animation created by displaying few still images successively. Listing 192 shows the complete
code for the Animator.java program:
Listing 192: The Animator.java Program
import java.io.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
public class Animator extends JApplet implements Runnable
{
Image img;
CircularList<Image> cl;
Thread thread;
String imageName = "Anim/anim";
String extension = ".jpg";
MediaTracker mt;
public Animator()
{
/*
Creating a CircularList object that stores image objects
*/
cl = new CircularList<Image>();
thread = new Thread(this);
mt = new MediaTracker(this);
}
public void init()
{
try{
URL docBase = getCodeBase();
String url = docBase.toString() + imageName;
for(int i = 1; i<= 8; i++)
{
String temp = url+String.valueOf(i)+extension;
System.out.println(temp);
img = getImage(new URL(temp));
mt.addImage(img, i);
cl.append(img);
}
}catch(MalformedURLException mue)
{
System.out.println(mue);
}
}
public void start()
{
cl.setTraverserToTop();
thread.start();
}
public void paint(Graphics g)
{
if(mt.checkAll())
{
g.drawImage(cl.getNext().getElement(), 0, 0, this);
}
}
public void run()
{
try{
mt.waitForAll();
while(true)
{
repaint();
Thread.sleep(50);
}
}catch(InterruptedException ie)
{
System.out.println(ie);
}
}
}
Reprinted for v697039, Verizon
In the above code, the Animator class extends the JApplet class and implements the Runnable
interface. This class reads images from the code base of the applet and stores them in a
CircularList object. The applet uses a MediaTracker object to read these images from the
CircularList object. The applet then traverses the CircularList object in an infinite loop to display the
animation.
The CircularList object includes a parameter of type Image. This means only objects of type Image
can be stored within the CircularList object. The thread object invokes the paint() method once in
every 50 milliseconds. The paint() method retrieves images from the CircularList object and paints
them on the screen. You do not have to add code that typecasts the object retrieved from the list
into an image. You use the objects directly.
Note To run this program, you should install JDK1.5 on your system. You can download the
JDK1.5 Beta from http://java.sun.com/j2se/1.5.0/download.jsp.
Compile all the class files using the javac command from the command line. Use a browser that
supports Java to view the applet. Figure 191 shows the Animator applet displaying the animation:
The above code uses typecasting inside a while loop. The objects contained in the ArrayList object
are strings, but you need to typecast these objects to use them. You can avoid such typecasting, if
you implement generic interfaces.
The above code defines two interfaces, CustomList and Traverser. These two interfaces are similar
to List and Iterator interfaces of the collection framework.
You can develop a CircularList class that implements the interfaces, CustomList and Traverser. The
CircularList class uses the Node class that represents the nodes of the CircularList object. Listing
195 shows the code for the CircularList class and the Node Class:
Listing 195: The CircularList and Node Classes
class Node<A>
{
A element;
Node<A> nextNode;
public Node()
{
element = null;
nextNode = null;
}
public Node(A elem, Node<A> n)
{
element = elem;
nextNode = n;
}
public void setElement(A elem)
{
element = elem;
}
public A getElement()
{
return element;
}
public void setNextNode(Node<A> n)
{
nextNode = n;
}
public Node<A> getNextNode()
{
return nextNode;
}
}
public class CircularList<A> implements CustomList<A>, Traverser<A>
{
protected Node<A> header;
protected Node<A> trailer;
protected Node<A> cursor;
protected Node<A> traverser;
protected int size;
public CircularList()
{
header = new Node<A>();
trailer = new Node<A>();
trailer.setNextNode(header);
cursor = null;
size = 0;
}
public int getSize()
{
return size;
}
public boolean isEmpty()
{
return (size == 0);
}
public void append(A elem)
{
Node<A> temp = new Node<A>();
temp.setElement(elem);
temp.setNextNode(trailer);
if(isEmpty())
{
header.setNextNode(temp);
}else
{
Reprinted for v697039, Verizon
10
cursor.setNextNode(temp);
}
cursor = temp;
size++;
}
public Node<A> getFirstNode()
{
return header;
}
public void setTraverserToTop()
{
traverser = header;
}
public Node<A> getLastNode()
{
return trailer;
}
public Node<A> getNext()
{
traverser = traverser.getNextNode();
if(traverser == header)
{
traverser = traverser.getNextNode();
}else if(traverser == trailer)
{
traverser = traverser.getNextNode().getNextNode();
}
return traverser;
}
public Traverser<A> getTraverser()
{
return this;
}
}
In the above code, the CircularList class implements the Traverser and CustomList interfaces. The
methods in these interfaces, append(A x), getTraverser(), setTraverserToTop(), and getNext(), are
overridden in the CircularList class.
The ParametrizedInterfaceTest program uses the CircularList class and the generic interfaces, as
shown in Listing 196:
Listing 196: The ParametrizedInterfaceTest Program
11
The ParameterizedInterfaceTest program contains a main() method, which creates two instances of
the CircularList class. One of these objects contains a list of String objects and the other object
contains a list of Integer objects. The methods declared in the interfaces, CustomList and Traverser
populate and traverse these lists. The program does not contain any typecasting code. Figure
192 shows the output of the ParametrizedInterfaceTest program:
12
The above snippet adds two String objects and an Integer object to a Vector object. However, the
Vector object recognizes the string and Integer objects as Object objects. You can add objects with
different data types into a single collection object in JDK 1.4.
In generics, subtyping is not covariant but is invariant. When subtyping is invariant, a subclass
object is not a type of its superclass object. Listing 198 shows an example of subtyping in
generics:
Listing 198: Subtyping in Generics
Vector<String> strVec = new Vector<String>();
String name = "Eric";
String city = "Boston";
Integer age = new Integer (25);
strVec.add(name);
strVec.add(city);
Vector<Object> objVec = strVec;
ObjVec.add(age);
The above snippet does not compile in JDK 1.5 with generics. The line Vector<Object> objVec =
strVec throws a compile time error message. This means in Java with generics a Vector of Strings
is not a Vector of Objects. You can generalize this by stating that if ABCD is a class, XYZ is a
subclass of ABCD, and E is a generic type declaration involving ABCD, then E<XYZ> is not a
subtype of E<ABCD>. The invariance of types is applicable only to generic types and normal class
subtypes are still covariant in JDK 1.5:
Irrespective of the type enclosed by a generic class, you cannot differentiate invocations to this
class that are generic, based on their parameter types. A Vector<String>.getClass() returns the
same value as the method Vector<Integer>.getClass(). The class information, obtained at runtime
for all objects of a generic class, is the same. This is because, at the time of compilation, the
compiler erases all generic information in the source code. As a result, the compiled bytecodes are
completely compatible with the older version of JRE and generic class behaves the same
regardless of the type that may be associated with it.
In JDK 1.5, it is not possible to invoke the instanceof operator on an object of a generic class, to
query about its type. The compiled bytecode does not retain parameter type information of generic
classes, therefore at runtime generic classes do not exist. The instanceof operator verifies if an
object belongs to a particular class at runtime. As a result, calling the instanceof operator with a
generic class throws a compile time error. For example, the following code throws a compile time
exception.
Vector<String> strings = new Vector<String>();
if(strings instanceof Vector<String>)
You should avoid typecasting of generic code. At runtime, all generic code is converted into normal
code. As a result, there does not exist any generic type information which you can typecast. Such
typecasting code raises an unchecked warning at compile time.
In addition, you cannot create arrays of generic types. This generates a compile time error.
However, you can declare arrays, which are generic.
Using Wildcards
The methods of the classes in the collections framework of JDK1.4 always receive and return an
object of type Object. This allows you to share elements of one collection with other collections. This
may not be directly possible when using generic collections. This is because, in generic collections,
an object of type Collection<String> is not the same as an object of type Collection<Object>. The
Collection<Object> type is an invariant collection of Objects.
To overcome the problem of invariance in objects, generics introduces bounding or constraining of
type parameters. A constraint, or a bound, is created using wildcards in generics.
14
In the above code, the printPay() method in the Payroll class fails due to the invariance of generics.
if you specify the parameter type as Collection<Object>, the method fails.
JDK 1.5 introduces the <?> notation, which represents an unknown type. Using this notation, you
write a collection of unknown type as Collection<?>. You can use this notation to represent a
collection of any type. You can also specify the upper and lower bounds to the unknown type.
These bounds provide information about the hierarchy of the unknown type to the compiler. The
notation <? extends A> specifies an upper bound, where the unknown type is any subtype of type
A. Similarly, the notation <? super A> specifies a lower bound, where the unknown type is super
type of type A.
Create a program that uses bounded wildcards and modify the printPay() method such that it takes
a lower bound parameter. The printPay method() prints the salary of all objects that are found in the
collection that it receives. The collection object holds objects of type Employee. You use a lower
bound to access these objects that are covariant. Listing 1910 shows the code for the
Employee.java program:
Listing 1910: The Employee Class
public abstract class Employee
{
double basic;
double deductions;
public abstract double calculatePay();
public Employee()
{
basic = 0;
deductions = 0;
}
public Employee(double b, double d)
{
basic = b;
deductions = d;
}
public void setBasic(double b)
{
basic = b;
}
public void setDeductions(double d)
{
deductions = d;
}
public double getBasic()
{
return basic;
}
public double getDeductions()
{
return deductions;
}
}
The above code shows the abstract class Employee. The abstract method calculatePay() in the
Employee class is implemented by the subclasses of this class. Listing 1911 shows the two
subclasses of the Employee class:
Listing 1911: The Manager and Clerk Classes
15
entertainmentAllowance = 0;
commissions = 0;
}
public Manager(double basic, double deductions, double travelAllowance, double
entertainmentAllowance, double commissions)
{
super(basic, deductions);
this.travelAllowance = travelAllowance;
this.entertainmentAllowance = entertainmentAllowance;
this.commissions = commissions;
}
public void setTravelAllowance(double ta)
{
travelAllowance = ta;
}
public void setEntertainmentAllowance(double ea)
{
entertainmentAllowance = ea;
}
public void setCommissions(double com)
{
commissions = com;
}
public double getTravelAllowance()
{
return travelAllowance;
}
public double getEntertainmentAllowance()
{
return entertainmentAllowance;
}
public double getCommissions()
{
return commissions;
}
public double calculatePay()
{
return ((getBasic() + travelAllowance + entertainmentAllowance + commissions)
getDeductions());
}
}
public class Clerk extends Employee
{
double overtimePay;
public Clerk()
{
super();
overtimePay = 0;
}
public Clerk(double basic, double deductions, double overtimePay)
{
super(basic, deductions);
this.overtimePay = overtimePay;
}
public void setOvertimePay(double otp)
{
overtimePay = otp;
}
public double getOvertimePay()
{
return overtimePay;
}
public double calculatePay()
{
return ((getBasic() + overtimePay) getDeductions());
}
}
In the above code, the Manager and Clerk classes are the subclasses of the Employee class.
These classes override the claculatePay() method in the Employee class. The managers salary
includes components that are not a part of the clerks salary. The Payroll program uses the
Reprinted for v697039, Verizon
16
Employee class and its subclasses to represent the collective payroll information of all employees.
This program uses a collection object to store information about all the employees and process their
salaries. Listing 1912 shows the code for the Payroll program:
Listing 1912: The Payroll Program
import java.util.*;
public class Payroll
{
public static void main(String args[])
{
ArrayList<Manager> managerList = new ArrayList<Manager>();
managerList.add(new Manager(4000, 1500, 800, 500, 1500));
managerList.add(new Manager(4200, 1500, 800, 500, 1700));
managerList.add(new Manager(4000, 1500, 800, 500, 1800));
managerList.add(new Manager(4300, 1500, 800, 500, 1600));
System.out.println("Pay of managers");
printPay(managerList);
ArrayList<Clerk> clerkList = new ArrayList<Clerk>();
clerkList.add(new Clerk(2500, 500, 1000));
clerkList.add(new Clerk(2700, 500, 1200));
clerkList.add(new Clerk(2400, 500, 1300));
clerkList.add(new Clerk(2800, 500, 0));
System.out.println("Pay of clerks");
printPay(clerkList);
}
public static void printPay(List<? extends Employee> e)
{
Iterator<? extends Employee> iter = e.iterator();
while(iter.hasNext())
{
System.out.println(iter.next().calculatePay());
}
}
}
In the above code, the bound for the printPay() is constructed such that all classes that are a
subclass of the Employee class can be passed as a parameter to the printPay() method. As a
result, you can pass a collection containing Manager objects or Clerk objects as parameter to the
printPay() method. Figure 193 shows the output of the payroll program:
17
import java.util.*;
public class Payroll2
{
public static void main(String args[])
{
ArrayList<Manager> managerList = new ArrayList<Manager>();
managerList.add(new Manager(4000, 1500, 800, 500, 1500));
managerList.add(new Manager(4200, 1500, 800, 500, 1700));
managerList.add(new Manager(4000, 1500, 800, 500, 1800));
managerList.add(new Manager(4300, 1500, 800, 500, 1600));
ArrayList<Clerk> clerkList = new ArrayList<Clerk>();
clerkList.add(new Clerk(2500, 500, 1000));
clerkList.add(new Clerk(2700, 500, 1200));
clerkList.add(new Clerk(2400, 500, 1300));
clerkList.add(new Clerk(2800, 500, 0));
ArrayList<Employee> empList = new ArrayList<Employee>();
mergeLists(empList, managerList);
mergeLists(empList, clerkList);
System.out.println("Pay of All Employees");
printPay(empList);
}
public static <T> void mergeLists(ArrayList<T> e1, ArrayList<? extends T>
e2)
{
e1.addAll(e2);
}
public static void printPay(List<Employee> e)
{
Iterator<? extends Employee> iter = e.iterator();
while(iter.hasNext())
{
System.out.println(iter.next().calculatePay());
}
}
}
In the above code, the mergeLists() method takes two ArrayList objects as parameters. The code
also shows the use of wildcards along with generic methods. Figure 194 shows the output of the
Payroll2 program:
18
Related Topics
For related information on this topic, you can refer to:
Understanding Classes and Objects in Java
Event and Exception Handling in Java