Generic
Class
What Are Generics
The term generics means
parameterized types. Using generics, it is possible to create a single class
that works with different types of data. A class, interface, or method that
operates on a parameterized type is called generic.
A Simple Generics Example
// T is a type
parameter that will be replaced by a real type
// when an object of
type Gen is created.
class Gen<T> {
T ob; // declare an object of type T
Gen(T o) {
ob = o;
}
// Return ob.
T getob() {
return ob;
}
// Show type of T.
void showType() {
System.out.println("Type of T is "
+ ob.getClass().getName());
}
}
public class
Main {
public static void
main(String args[]) {
Gen<Integer> iOb = new
Gen<Integer>(88);
iOb.showType();
int v = iOb.getob();
System.out.println("value: "
+ v);
Gen<String> strOb = new
Gen<String>("Generics Test");
strOb.showType();
String str = strOb.getob();
System.out.println("value: "
+ str);
}
}
T is the name of a type parameter. T
is used to declare an object. Generics work only with objects Generic types
differ based on their type arguments
A Generic Class with Two Type
Parameters
You can declare more than one type
parameter in a generic type.
// A simple generic
class with two type parameters: T and V.
class TwoGen<T, V> {
T ob1;
V ob2;
TwoGen(T o1, V o2) {
ob1 = o1;
ob2 = o2;
}
void showTypes() {
System.out.println("Type of T is "
+ ob1.getClass().getName());
System.out.println("Type of V is "
+ ob2.getClass().getName());
}
T getob1() {
return ob1;
}
V getob2() {
return ob2;
}
}
public class
Main {
public static void
main(String args[]) {
TwoGen<Integer, String> tgObj = new
TwoGen<Integer, String>(88, "Generics");
tgObj.showTypes();
int v = tgObj.getob1();
System.out.println("value: "
+ v);
String str = tgObj.getob2();
System.out.println("value: "
+ str);
}
}
Here is the syntax for declaring a
generic class:
class className<type-param-list> {}
Here is the syntax for declaring a
reference to a generic class:
className<type-arg-list>
varName = new
className<type-arg-list>(cons-arg-list);
The following code declares and uses
a Queue<E> generic type.
class Queue<E> {
private E[] elements;
private int head=0, tail=0;
Queue(int size) {
elements = (E[]) new
Object[size];
}
void insert(E element) throws QueueFullException {
if (isFull())
throw new QueueFullException();
elements[tail] = element;
tail = (tail + 1) % elements.length;
}
E remove() throws
QueueEmptyException {
if (isEmpty()){
throw new QueueEmptyException();
}
E element = elements[head];
head = (head + 1) % elements.length;
return element;
}
boolean isEmpty() {
return head == tail;
}
boolean isFull() {
return (tail + 1) %
elements.length == head;
}
}
class QueueEmptyException extends Exception {
}
class QueueFullException extends Exception {
}
public class
Main{
public static void
main(String[] args) throws
QueueFullException, QueueEmptyException {
Queue<String> queue = new
Queue<String>(6);
System.out.println("Empty: "
+ queue.isEmpty());
System.out.println("Full: "
+ queue.isFull());
queue.insert("A");
queue.insert("B");
queue.insert("C");
queue.insert("D");
queue.insert("E");
System.out.println("Empty: "
+ queue.isEmpty());
System.out.println("Full: "
+ queue.isFull());
System.out.println("Removing "
+ queue.remove());
System.out.println("Empty: "
+ queue.isEmpty());
System.out.println("Full: "
+ queue.isFull());
System.out.println("Adding F");
queue.insert("F");
while (!queue.isEmpty()){
System.out.println("Removing "
+ queue.remove());
}
System.out.println("Empty: "
+ queue.isEmpty());
System.out.println("Full: "
+ queue.isFull());
}
}
Output:
Empty: true
Full: false
Empty: false
Full: true
Removing A
Empty: false
Full: false
Adding F
Removing B
Removing C
Removing D
Removing E
Removing F
Empty: true
Full: false
Generic Bounded Types
When specifying a type parameter,
you can create an upper bound from which all type arguments must be derived.
This is accomplished through the use of an extends clause when specifying the
type parameter:
<T extends superClassName>
This specifies that T can only be
replaced by superClassName, or subclasses of superClassName. Thus, superclass
defines an inclusive, upper limit.
class Calculator<T extends Number> {
T[] nums;
Calculator(T[] o) {
nums = o;
}
double average() {
double sum = 0.0;
for (int i = 0; i < nums.length; i++){
sum += nums[i].doubleValue();
}
return sum / nums.length;
}
}
public class Main {
public static void main(String args[]) {
Integer inums[] = { 1, 2, 3, 4, 5 };
Calculator<Integer> iob = new Calculator<Integer>(inums);
double v = iob.average();
System.out.println("iob average is " + v);
Double dnums[] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
Calculator<Double> dob = new Calculator<Double>(dnums);
double w = dob.average();
System.out.println("dob average is " + w);
}
}
In addition to using a class type
as a bound, you can also use an interface type. You can specify multiple
interfaces as bounds.
A bound can include both a class
type and one or more interfaces. In this case, the class type must be specified
first.
When a bound includes an interface
type, only type arguments that implement that interface are legal.
When specifying a bound that has a
class and an interface, or multiple interfaces, use the & operator to
connect them.
For example,
class Gen<T extends MyClass & MyInterface> {}
Any type argument passed to T must
be a subclass of MyClass and implement MyInterface.
Generic Wildcard Arguments
To create a generic method, you use
the wildcard argument. The wildcard argument is specified by the ?, and it
represents an unknown type.
class Calculator<T extends Number> {
T[] nums;
Calculator(T[] o) {
nums = o;
}
double average() {
double sum = 0.0;
for (int i = 0; i < nums.length; i++){
sum += nums[i].doubleValue();
}
return sum / nums.length;
}
}
public class Main {
boolean sameAvg(Calculator<?> ob) {
if (1.2 == ob.average())
return true;
return false;
}
public static void main(String args[]) {
}
}
Calculator<?> matches any
Stats object, allowing any two Stats objects to have their averages compared.
The following program demonstrates
this:
class Calculator<T extends Number> {
T[] nums;
Calculator(T[] o) {
nums = o;
}
double average() {
double sum = 0.0;
for (int i = 0; i < nums.length; i++)
sum += nums[i].doubleValue();
return sum / nums.length;
}
boolean sameAvg(Calculator<?> ob) {
if (average() == ob.average())
return true;
return false;
}
}
public class Main {
public static void main(String args[]) {
Integer inums[] = { 1, 2, 3, 4, 5 };
Calculator<Integer> iob = new Calculator<Integer>(inums);
Double dnums[] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
Calculator<Double> dob = new Calculator<Double>(dnums);
if (iob.sameAvg(dob))
System.out.println("are the same.");
else
System.out.println("differ.");
}
}
Generic Bounded Wildcards
Wildcard arguments can be bounded
in the same way that a type parameter can be bounded. A bounded wildcard is
important when creating a generic type that will operate on a class hierarchy.
A bounded wildcard specifies either
an upper bound or a lower bound for the type argument.
class TwoD {
int x, y;
TwoD(int a, int b) {
x = a;
y = b;
}
}
// Three-dimensional coordinates.
class ThreeD extends TwoD {
int z;
ThreeD(int a, int b, int c) {
super(a, b);
z = c;
}
}
class Map<T extends TwoD> {
T[] coords;
Map(T[] o) {
coords = o;
}
}
public class Main {
static void showXY(Map<?> c) {
for (int i = 0; i < c.coords.length; i++){
System.out.println(c.coords[i].x + " " + c.coords[i].y);
}
}
static void showXYZ(Map<? extends ThreeD> c) {
for (int i = 0; i < c.coords.length; i++){
System.out.println(c.coords[i].x + " " + c.coords[i].y + " "
+ c.coords[i].z);
}
}
public static void main(String args[]) {
TwoD td[] = { new TwoD(0, 0), new TwoD(-1, -2) };
Map<TwoD> map = new Map<TwoD>(td);
System.out.println("Contents of tdlocs.");
showXY(map);
}
}
In general, to establish an upper
bound for a wildcard, use the following type of wildcard expression:
<? extends superclass>
superclass is the name of the class
that serves as the upper bound. This is an inclusive clause.
You can specify a lower bound for a
wildcard by adding a super clause to a wildcard declaration.
<? super subclass>
In this case, only classes that are
superclasses of subclass are acceptable arguments. This is an exclusive clause,
because it will not match the class specified by subclass.
Generic Method
It is possible to create a generic
method that is enclosed within a non-generic class.
public class Main {
static <T, V extends T> boolean isIn(T x, V[] y) {
for (int i = 0; i < y.length; i++) {
if (x.equals(y[i])) {
return true;
}
}
return false;
}
public static void main(String args[]) {
Integer nums[] = { 1, 2, 3, 4, 5 };
if (isIn(2, nums)){
System.out.println("2 is in nums");
}
String strs[] = { "one", "two", "three", "four", "five" };
if (isIn("two", strs)){
System.out.println("two is in strs");
}
}
}
The following code declares a
copyList() generic method.
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> ls = new ArrayList<String>();
ls.add("A");
ls.add("B");
ls.add("C");
List<String> lsCopy = new ArrayList<String>();
copyList(ls, lsCopy);
List<Integer> lc = new ArrayList<Integer>();
lc.add(0);
lc.add(5);
List<Integer> lcCopy = new ArrayList<Integer>();
copyList(lc, lcCopy);
}
static <T> void copyList(List<T> src, List<T> dest) {
for (int i = 0; i < src.size(); i++)
dest.add(src.get(i));
}
}
Generic Constructors
It is possible for constructors to
be generic, even if their class is not. For example, consider the following
short program:
class MyClass {
private double val;
<T extends Number> MyClass(T arg) {
val = arg.doubleValue();
}
void showval() {
System.out.println("val: " + val);
}
}
public class Main {
public static void main(String args[]) {
MyClass test = new MyClass(100);
MyClass test2 = new MyClass(123.5F);
test.showval();
test2.showval();
}
}
Generic Interfaces
Generic interfaces are specified
like generic classes.
interface MinMax<T extends Comparable<T>> {
T max();
}
class MyClass<T extends Comparable<T>> implements MinMax<T> {
T[] vals;
MyClass(T[] o) {
vals = o;
}
public T max() {
T v = vals[0];
for (int i = 1; i < vals.length; i++) {
if (vals[i].compareTo(v) > 0) {
v = vals[i];
}
}
return v;
}
}
public class Main {
public static void main(String args[]) {
Integer inums[] = { 3, 6, 2, 8, 6 };
Character chs[] = { 'b', 'r', 'p', 'w' };
MyClass<Integer> a = new MyClass<Integer>(inums);
MyClass<Character> b = new MyClass<Character>(chs);
System.out.println(a.max());
System.out.println(b.max());
}
}
In general, if a class implements a
generic interface, then that class must also be generic.
If a class implements a specific
type of generic interface, such as shown here:
class MyClass implements MinMax<Integer> { // OK
then the implementing class does
not need to be generic.
Here is the generalized syntax for
a generic interface:
interface interface-name<type-param-list> { // ...
type-param-list is a
comma-separated list of type parameters. When a generic interface is
implemented, you must specify the type arguments, as shown here:
class class-name<type-param-list>
implements interface-name<type-arg-list> {
Raw Types and Legacy Code
To handle the transition to
generics, Java allows a generic class to be used without any type arguments.
Here is an example that shows a raw
type in action:
class MyClass<T> {
T ob;
MyClass(T o) {
ob = o;
}
T getob() {
return ob;
}
}
public class Main {
public static void main(String args[]) {
MyClass raw = new MyClass(new Double(98.6));
double d = (Double) raw.getob();
System.out.println("value: " + d);
}
}
Generic Class Hierarchies
A generic class can act as a
superclass or be a subclass. In a generic hierarchy, any type arguments needed
by a generic superclass must be passed up the hierarchy by all subclasses.
Using a Generic Superclass
class MyClass<T> {
T ob;
MyClass(T o) {
ob = o;
}
T getob() {
return ob;
}
}
class MySubclass<T, V> extends MyClass<T> {
V ob2;
MySubclass(T o, V o2) {
super(o);
ob2 = o2;
}
V getob2() {
return ob2;
}
}
public class Main {
public static void main(String args[]) {
MySubclass<String, Integer> x = new MySubclass<String, Integer>("Value is: ", 99);
System.out.print(x.getob());
System.out.println(x.getob2());
}
}
A Generic Subclass
It is perfectly acceptable for a
non-generic class to be the superclass of a generic subclass.
class MyClass {
int num;
MyClass(int i) {
num = i;
}
int getnum() {
return num;
}
}
class MySubclass<T> extends MyClass {
T ob;
MySubclass(T o, int i) {
super(i);
ob = o;
}
T getob() {
return ob;
}
}
public class Main {
public static void main(String args[]) {
MySubclass<String> w = new MySubclass<String>("Hello", 4);
System.out.print(w.getob() + " ");
System.out.println(w.getnum());
}
}
Run-Time Type Comparisons Within a
Generic Hierarchy
The instanceof operator can be applied
to objects of generic classes.
class Gen<T> {
T ob;
Gen(T o) {
ob = o;
}
T getob() {
return ob;
}
}
class Gen2<T> extends Gen<T> {
Gen2(T o) {
super(o);
}
}
public class Main {
public static void main(String args[]) {
Gen<Integer> iOb = new Gen<Integer>(88);
Gen2<Integer> iOb2 = new Gen2<Integer>(99);
Gen2<String> strOb2 = new Gen2<String>("Generics Test");
System.out.println("iOb2 is instance of Gen2"+(iOb2 instanceof Gen2<?>));
System.out.println("iOb2 is instance of Gen"+(iOb2 instanceof Gen<?>));
System.out.println("strOb2 is instance of Gen2"+(strOb2 instanceof Gen2<?>));
System.out.println("strOb2 is instance of Gen"+(strOb2 instanceof Gen<?>));
System.out.println("iOb is instance of Gen2"+(iOb instanceof Gen2<?>));
System.out.println("iOb is instance of Gen"+(iOb instanceof Gen<?>));
}
}
Casting
You can cast one instance of a
generic class into another only if the two are compatible and their type
arguments are the same.
For example, assuming the foregoing
program, this cast is legal:
class Gen<T> {
T ob;
Gen(T o) {
ob = o;
}
T getob() {
return ob;
}
}
class Gen2<T> extends Gen<T> {
Gen2(T o) {
super(o);
}
}
public class Main {
public static void main(String args[]) {
Gen<Integer> iOb = new Gen<Integer>(88);
Gen2<Integer> iOb2 = new Gen2<Integer>(99);
Gen2<String> strOb2 = new Gen2<String>("Generics Test");
iOb = (Gen<Integer>) iOb2;
}
}
because iOb2 is an instance of
Gen<Integer>. But, this cast:
class Gen<T> {
T ob;
Gen(T o) {
ob = o;
}
T getob() {
return ob;
}
}
class Gen2<T> extends Gen<T> {
Gen2(T o) {
super(o);
}
}
public class Main {
public static void main(String args[]) {
Gen<Integer> iOb = new Gen<Integer>(88);
Gen2<Integer> iOb2 = new Gen2<Integer>(99);
Gen2<String> strOb2 = new Gen2<String>("Generics Test");
//iOb = (Gen<Long>) iOb2;//wrong
}
}
is not legal because iOb2 is not an
instance of Gen<Long>.
Overriding Methods in a Generic
Class
A method in a generic class can be
overridden like any other method.
class Gen<T> {
T obj;
Gen(T o) {
obj = o;
}
T getob() {
System.out.print("Gen's getob(): ");
return obj;
}
}
class Gen2<T> extends Gen<T> {
Gen2(T o) {
super(o);
}
T getob() {
System.out.print("Gen2's getob(): ");
return obj;
}
}
public class Main {
public static void main(String args[]) {
Gen<Integer> iOb = new Gen<Integer>(88);
Gen2<String> strOb2 = new Gen2<String>("Generics Test");
System.out.println(iOb.getob());
System.out.println(strOb2.getob());
}
}
Generic Restrictions
Type Parameters Can't Be
Instantiated
It is not possible to create an
instance of a type parameter. For example, consider this class:
// Can't create an instance of T.
class Gen<T> {
T ob;
Gen() {
ob = new T(); // Illegal!!!
}
}
Restrictions on Static Members
No static member can use a type
parameter declared by the enclosing class. For example, all of the static
members of this class are illegal:
class Wrong<T> {
// Wrong, no static variables of type T.
static T ob;
// Wrong, no static method can use T.
static T getob() {
return ob;
}
// Wrong, no static method can access object of type T.
static void showob() {
System.out.println(ob);
}
}
You can declare static generic methods with their own type parameters.
Generic Array Restrictions
You cannot instantiate an array
whose base type is a type parameter. You cannot create an array of type
specific generic references.
The following short program shows
both situations:
class MyClass<T extends Number> {
T ob;
T vals[];
MyClass(T o, T[] nums) {
ob = o;
vals = nums;
}
}
public class Main {
public static void main(String args[]) {
Integer n[] = { 1 };
MyClass<Integer> iOb = new MyClass<Integer>(50, n);
// Can't create an array of type-specific generic references.
// Gen<Integer> gens[] = new Gen<Integer>[10];
MyClass<?> gens[] = new MyClass<?>[10]; // OK
}
}