Generics

Generic Fundamentals

  • The term generics means parameterized types. 
  • Using generics, it is possible to create a single class, for example, that automatically works with different types of data.  
  • A class, interface, or method that operates on a parameterized type is called generic, as in generic class or generic method. 
  • Generics added the type safety. 
  • With generics, all casts are automatic and implicit. 

A Simple Generic Example

/*
 * A simple generic class. Here, T is a type parameter that will be replaced by
 * a real type when an object of type Gen is created.
 */
public class MyGen<T> {
	// attributes
	T value;

	// constructor
	public MyGen(T value) {
		this.value = value;
	}

	// method
	public T getValue() {
		return value;
	}

	public void showType() {
		System.out.println("Type T is " + value.getClass().getName());
	}
}
public class GenericExample {
	public static void main(String[] args) {
		// integer
		MyGen<Integer> intGen = new MyGen<Integer>(90);
		System.out.println("value is " + intGen.getValue());
		intGen.showType();

		// string
		MyGen<String> strGen = new MyGen<String>("hello");
		System.out.println("value is " + strGen.getValue());
		strGen.showType();

		// box
		MyGen<Box> boxGen = new MyGen<Box>(new Box(2, 3, 4));
		System.out.println("Box volume is " + boxGen.getValue().volume()); // call method on box object
		boxGen.showType();
	}
}

Bounded Types

Sometimes it is useful to limit the types that can be passed to a type parameter. For example, assume that you want to create a generic class that contains a method that returns the average of an array of numbers. Furthermore, you want to use the class to obtain the average of an array of any type of number, including integers, floats, and doubles. Thus, you want to specify the type of the numbers generically, using a type parameter. 

/*
 * Bounded Type generic class Rule: T must be a Number or any classes than
 * extends Number class (subclasses)
 */
public class MyStats<T extends Number> {

	// instance variables
	// array of numbers
	private T[] numbers;

	// constructor
	public MyStats(T[] numbers) {
		this.numbers = numbers;
	}

	// methods - calculate average
	public double average() {
		double sum = 0.0;
		for (int i = 0; i < numbers.length; i++) {
			sum += numbers[i].doubleValue();
		}
		return sum / numbers.length;
	}
}
public class BoundedTypeExample {
	public static void main(String[] args) {
		// integer
		Integer[] integers = { 1, 2, 3, 4, 5 };
		MyStats<Integer> first = new MyStats<Integer>(integers);
		System.out.println("Average of integers is " + first.average());

		// double
		Double[] doubles = { 2.3, 1.4, 5.6, 7.8 };
		MyStats<Double> second = new MyStats<Double>(doubles);
		System.out.println("Average of doubles is " + second.average());

		// string - this will give error since string is not number
		String[] names = { "John", "Bob", "Charles" };
//				MyStats<String> third = new MyStats<String>(names);
	}
}

Wildcard Arguments

public class MyStats<T extends Number> {

	// instance variables
	// array of numbers
	private T[] numbers;

	// constructor
	public MyStats(T[] numbers) {
		this.numbers = numbers;
	}

	// methods - calculate average
	public double average() {
		double sum = 0.0;
		for (int i = 0; i < numbers.length; i++) {
			sum += numbers[i].doubleValue();
		}
		return sum / numbers.length;
	}

	// method with wildcard argument
	public boolean sameAverage(MyStats<?> s) {
		// check if average values are same
		if (average() == s.average()) {
			return true;
		}
		return false;
	}
}
public class WildcardExample {
	public static void main(String[] args) {
		// all other codes here
		
		// compare average between integers and doubles
		// first - integer
		// second - double

		// call sameAverage from first object and pass second as parameter
		if (first.sameAverage(second)) {
			System.out.println("same average");
		} else {
			System.out.println("different average");
		}

		// let's swap
		// call sameAverage from second object and pass first as parameter
		if (second.sameAverage(first)) {
			System.out.println("same average");
		} else {
			System.out.println("different average");
		}
	}
}

Here, Stats<?> matches any Stats object, allowing any two Stats objects to have their averages compared. 

One last point: It is important to understand that the wildcard does not affect what type of Stats objects can be created. This is governed by the extends clause in the Stats declaration. The wildcard simply matches any valid Stats object. 

Bounded Wildcard

Wildcard arguments can be bounded in much the same way that a type parameter can be bounded. A bounded wildcard is especially important when you are creating a generic type that will operate on a class hierarchy. 

Consider the following hierarchy of classes that encapsulate coordinates: 

//TwoD class contains 2 integers a and b
class TwoDim {
	public int a, b;

	public TwoDim(int a, int b) {
		this.a = a;
		this.b = b;
	}
}

//ThreeD is a subclass of TwoD, this class has it's own data called c
class ThreeDim extends TwoDim {
	public int c;

	public ThreeDim(int a, int b, int c) {
		super(a, b);
		this.c = c;
	}
}

//FourD is a subclass of ThreeD, this class also has it's own data called d
class FourDim extends ThreeDim {
	public int d;

	public FourDim(int a, int b, int c, int d) {
		super(a, b, c);
		this.d = d;
	}
}

At the top of the hierarchy is TwoDim, which encapsulates a two-dimensional, AB coordinate. TwoDim is inherited by ThreeDim, which adds a third dimension, creating an ABC coordinate. ThreeDim is inherited by FourDim, which adds a fourth dimension (time), yielding a four-dimensional coordinate. 

Shown next is a generic class called Coords, which stores an array of coordinates: 

//bounded type generic class
class Coordinates<T extends TwoDim> {
	// array of coordinates object
	public T[] list;

	public Coordinates(T[] list) {
		this.list = list;
	}
}

Notice that Coords specifies a type parameter bounded by TwoDim. This means that any array stored in a Coords object will contain objects of type TwoDim or one of its subclasses. 

Now, assume that you want to write a method that displays the A and B coordinates for each element in the coords array of a Coords object. Because all types of Coords objects have at least two coordinates (A and B), this is easy to do using a wildcard, as shown here: 

    // wildcard argument
	public static void showAB(Coordinates<?> c) {
		System.out.println("A B coordinates");
		for (int i = 0; i < c.list.length; i++) {
			System.out.println(c.list[i].a + " " + c.list[i].b);
		}
		System.out.println(); // empty line
	}

Because Coords is a bounded generic type that specifies TwoDim as an upper bound, all objects that can be used to create a Coords object will be arrays of type TwoDim, or of classes derived from TwoDim. Thus, showAB( ) can display the contents of any Coords object. 

However, what if you want to create a method that displays the A, B, and C coordinates of a ThreeDim or FourDim object? The trouble is that not all Coords objects will have three coordinates, because a Coords<TwoDim> object will only have A and B. Therefore, how do you write a method that displays the A, B, and C coordinates for Coords<ThreeDim> and Coords<FourDim> objects, while preventing that method from being used with Coords<TwoDim> objects? The answer is the bounded wildcard argument. 

A bounded wildcard specifies either an upper bound or a lower bound for the type argument. This enables you to restrict the types of objects upon which a method will operate. The most common bounded wildcard is the upper bound, which is created using an extends clause in much the same way it is used to create a bounded type. 

Using a bounded wildcard, it is easy to create a method that displays the A, B, and C coordinates of a Coords object, if that object actually has those three coordinates. For example, the following showABC( ) method shows the A, B, and C coordinates of the elements stored in a Coords object, if those elements are actually of type ThreeDim (or are derived from ThreeDim): 

    // bounded wildcard
public static void showABC(Coordinates<? extends ThreeDim> c) {
System.out.println("A B C coordinates");
for (int i = 0; i < c.list.length; i++) {
System.out.println(c.list[i].a + " " + c.list[i].b + " " + c.list[i].c);
}
System.out.println(); // empty line
}
public class BoundedWildcardExample {
	public static void main(String[] args) {
		// define list of objects
		TwoDim[] first = { new TwoDim(1, 2), new TwoDim(3, 4), new TwoDim(5, 6) };
		ThreeDim[] second = { new ThreeDim(1, 2, 1), new ThreeDim(3, 4, 3), new ThreeDim(5, 6, 5) };
		FourDim[] third = { new FourDim(1, 2, 1, 2), new FourDim(3, 4, 3, 4), new FourDim(5, 6, 5, 6) };

		// create coordinate object
		Coordinates<TwoDim> coords1 = new Coordinates<TwoDim>(first);
		Coordinates<ThreeDim> coords2 = new Coordinates<ThreeDim>(second);
		Coordinates<FourDim> coords3 = new Coordinates<FourDim>(third);

		// call method with wildcard argument
		showAB(coords1); // TwoDim
		showAB(coords2); // ThreeDim
		showAB(coords3); // FourDim

		// call method with bounded wildcard
//				showABC(coords1);   // TwoDim - error
		showABC(coords2); // ThreeDim
		showABC(coords3); // FourDim
	}
}